<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Lorenzo Rovigatti</title><link>https://www.roma1.infn.it/~rovigatl/</link><description>LR's (rarely updated) blog</description><atom:link href="https://www.roma1.infn.it/~rovigatl/rss.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2025 &lt;a href="mailto:lorenzo.rovigatti@uniroma1.it"&gt;Lorenzo Rovigatti&lt;/a&gt; </copyright><lastBuildDate>Thu, 06 Nov 2025 13:35:38 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Building a Python Package with CMake, pybind11, and scikit-build-core</title><link>https://www.roma1.infn.it/~rovigatl/posts/pybind_cmake/</link><dc:creator>Lorenzo Rovigatti</dc:creator><description>&lt;main&gt;


&lt;nav class="contents" id="table-of-contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;Table of Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/pybind_cmake/#building-a-python-package-with-cmake-pybind11-and-scikit-build-core" id="toc-entry-1"&gt;Building a Python Package with CMake, pybind11, and scikit-build-core&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/pybind_cmake/#project-layout" id="toc-entry-2"&gt;Project layout&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/pybind_cmake/#module-source-files" id="toc-entry-3"&gt;Module source files&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/pybind_cmake/#cmakelists-txt" id="toc-entry-4"&gt;CMakeLists.txt&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/pybind_cmake/#pyproject-toml" id="toc-entry-5"&gt;pyproject.toml&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/pybind_cmake/#continuous-integration" id="toc-entry-6"&gt;Continuous integration&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="building-a-python-package-with-cmake-pybind11-and-scikit-build-core"&gt;
&lt;h3&gt;Building a Python Package with CMake, pybind11, and scikit-build-core&lt;/h3&gt;
&lt;p&gt;I’m the main developer and maintainer of &lt;a class="reference external" href="https://github.com/lorenzo-rovigatti/oxDNA/"&gt;oxDNA&lt;/a&gt;, a simulation engine implementing oxDNA and oxRNA, two of the most widely-used coarse-grained models used in nucleic-acid nanotechnology. The software has been used in &lt;a class="reference external" href="https://dna.physics.ox.ac.uk/index.php?title=Publications"&gt;hundreds of publications&lt;/a&gt;, and it is part of a larger ecosystem (see &lt;em&gt;e.g.&lt;/em&gt; &lt;a class="reference external" href="https://oxdna.org/"&gt;the oxDNA.org webserver&lt;/a&gt;, or &lt;a class="reference external" href="https://sulcgroup.github.io/oxdna-viewer/"&gt;oxview&lt;/a&gt;, a browser-based editing/visualising tool).&lt;/p&gt;
&lt;p&gt;A few years ago I started working making some of oxDNA’s features available to Python through &lt;a class="reference external" href="https://github.com/pybind/pybind11"&gt;pybind11&lt;/a&gt;. As a result, recent oxDNA versions can optionally compile and install an &lt;code class="docutils literal"&gt;oxpy&lt;/code&gt; module (which, for instance, can be used to &lt;a class="reference external" href="https://lorenzo-rovigatti.github.io/oxDNA/oat/index.html"&gt;streamline analysis&lt;/a&gt; of simulation results, or run &lt;a class="reference external" href="https://github.com/lorenzo-rovigatti/oxDNA/tree/master/examples/METADYNAMICS"&gt;metadynamics&lt;/a&gt; simulations). For practical reasons, it is possible to install &lt;code class="docutils literal"&gt;oxpy&lt;/code&gt; in two ways:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;By using pip:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="code literal-block"&gt;&lt;code&gt;pip install . # from the root folder
&lt;/code&gt;&lt;/pre&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;&lt;p&gt;By using the usual CMake pipeline&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="code literal-block"&gt;&lt;code&gt;mkdir build
cd build
cmake .. -DPython=On # you can add more options, such as -DCUDA=On
make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The requirement of having two ways of compiling the code forced me to conjure up, mostly by trial and error, a rather obscure compilation process. Unfortunately (but perhaps not unsurprisingly), it turns out that it procedure was not only obscure, but also fragile, as &lt;a class="reference external" href="https://github.com/lorenzo-rovigatti/oxDNA/issues/179"&gt;upgrading pip broke it&lt;/a&gt;. I then spent a weekend trying to fix things up. This post is the result of this effort, and I decided to write mostly to help my future self. Throughout the post I’ll be using PIP and CMAKE to refer to the two installation methods introduced before, respectively.&lt;/p&gt;
&lt;p&gt;Here I will explain how to create a Python package that includes a C++ extension built with CMake and pybind11, and how to package it cleanly using &lt;a class="reference external" href="https://github.com/scikit-build/scikit-build-core"&gt;scikit-build-core&lt;/a&gt; so that it fulfills my (oxDNA’s?) needs.&lt;/p&gt;
&lt;p&gt;A tar file containing a full example (&lt;em&gt;e.g.&lt;/em&gt; all the files described in this post) can be downloaded &lt;a href="https://www.roma1.infn.it/~rovigatl/oxpy_mwe.tgz"&gt;here&lt;/a&gt;. The example combines:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;CMake for building the C++ extension&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;pybind11 for Python bindings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scikit-build-core for packaging&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;setuptools_scm for automatic versioning from git tags/commits&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="project-layout"&gt;
&lt;h4&gt;Project layout&lt;/h4&gt;
&lt;pre class="code literal-block"&gt;&lt;code&gt;oxpy-mwe/
├─ CMakeLists.txt
├─ example.py
├─ pyproject.toml
├─ README.md
└─ src/
   └─ oxpy_mwe/
      ├─ __init__.py
      ├─ core.cpp
      └─ pyproject_cmake.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;section id="module-source-files"&gt;
&lt;h4&gt;Module source files&lt;/h4&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;src/oxpy_mwe/core.cpp&lt;/code&gt; file uses pybind11 to create one module that exposes a simple function:&lt;/p&gt;
&lt;pre class="code c++ literal-block"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;pybind11/pybind11.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pybind11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PYBIND11_MODULE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"oxpy_mwe core module"&lt;/s&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;"add"&lt;/s&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"Add two integers"&lt;/s&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we create an &lt;code class="docutils literal"&gt;__init__.py&lt;/code&gt; file that imports the &lt;code class="docutils literal"&gt;add&lt;/code&gt; function from the core module and handles the module version by importing a &lt;code class="docutils literal"&gt;_version.py&lt;/code&gt; that will be generated at compile time:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;._version&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;__version__&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;__version__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0"&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;section id="cmakelists-txt"&gt;
&lt;h4&gt;CMakeLists.txt&lt;/h4&gt;
&lt;p&gt;Here I split the file in a few sections, so that they can be commented separately.&lt;/p&gt;
&lt;pre class="code cmake literal-block"&gt;&lt;code&gt;&lt;span class="nb"&gt;cmake_minimum_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;VERSION&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;3.18&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;oxpy_mwe&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;LANGUAGES&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;CXX&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;Python&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;COMPONENTS&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;Interpreter&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;Development.Module&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;REQUIRED&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;pybind11&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;CONFIG&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;QUIET&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;NOT&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;pybind11_FOUND&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;FetchContent&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;FetchContent_Declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;s&gt;pybind11&lt;/s&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;s&gt;GIT_REPOSITORY&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;https://github.com/pybind/pybind11.git&lt;/s&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;s&gt;GIT_TAG&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;v2.12.0&lt;/s&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;FetchContent_MakeAvailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;pybind11&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We look for python and pybind11 (which is fetched from the internet if not found on the system).&lt;/p&gt;
&lt;pre class="code cmake literal-block"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a staging directory for the Python package
&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;OXPY_BASE_DIR&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${CMAKE_BINARY_DIR}/python"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;OXPY_PACKAGE_DIR&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${OXPY_BASE_DIR}/oxpy_mwe"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Make sure the directory exists
&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;MAKE_DIRECTORY&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OXPY_PACKAGE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We create a &lt;code class="docutils literal"&gt;python/oxpy_mwe&lt;/code&gt; folder relative to the building directory where all the package files will be stored.&lt;/p&gt;
&lt;pre class="code cmake literal-block"&gt;&lt;code&gt;&lt;span class="nb"&gt;pybind11_add_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;core&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;MODULE&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;src/oxpy_mwe/core.cpp&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Place the compiled extension (.so) in that folder
&lt;/span&gt;&lt;span class="nb"&gt;set_target_properties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;core&lt;/s&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;s&gt;PROPERTIES&lt;/s&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;s&gt;LIBRARY_OUTPUT_DIRECTORY&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${OXPY_PACKAGE_DIR}"&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;s&gt;SUFFIX&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".so"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We tell CMake to compile the module, make it a shared &lt;code class="docutils literal"&gt;.so&lt;/code&gt; object and put it in the output folder (&lt;em&gt;i.e.&lt;/em&gt; &lt;code class="docutils literal"&gt;python/oxpy_mwe&lt;/code&gt;).&lt;/p&gt;
&lt;pre class="code cmake literal-block"&gt;&lt;code&gt;&lt;span class="nb"&gt;configure_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;src/oxpy_mwe/__init__.py&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OXPY_PACKAGE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;s&gt;/__init__.py&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;COPYONLY&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We copy &lt;code class="docutils literal"&gt;__init__.py&lt;/code&gt; to the output folder.&lt;/p&gt;
&lt;pre class="code cmake literal-block"&gt;&lt;code&gt;&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;SKBUILD&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# copy the _version.py file generated by setuptools_scm
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;configure_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;s&gt;/src/oxpy_mwe/_version.py&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OXPY_PACKAGE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;s&gt;/_version.py&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;COPYONLY&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# install the package to a location where scikit-build can find it
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;DIRECTORY&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OXPY_PACKAGE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;s&gt;/&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;DESTINATION&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;oxpy_mwe&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;else&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;configure_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;s&gt;/README.md&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OXPY_PACKAGE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;s&gt;/README.md&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;COPYONLY&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;configure_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;s&gt;/src/oxpy_mwe/pyproject_cmake.toml&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OXPY_BASE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;s&gt;/pyproject.toml&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;# Install the package using pip
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;OXPY_SYSTEM_INSTALL&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Install system-wide via pip"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;OFF&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;OXPY_SYSTEM_INSTALL&lt;/s&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;CODE&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"execute_process(COMMAND ${Python_EXECUTABLE} -m pip install ${OXPY_BASE_DIR})"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;else&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;s&gt;CODE&lt;/s&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"execute_process(COMMAND ${Python_EXECUTABLE} -m pip install --user ${OXPY_BASE_DIR})"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the compilation proceeds differently for PIP and CMAKE: in the former case pip uses scikit-build-core, which defines a CMake variable SKBUILD that is used to determine what to do:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;In the case of PIP, the &lt;code class="docutils literal"&gt;_version.py&lt;/code&gt; file, generated by &lt;code class="docutils literal"&gt;setuptools_scm&lt;/code&gt;, see below, is copied from the &lt;code class="docutils literal"&gt;/src/oxpy_mwe/&lt;/code&gt; folder to the output folder. Then, when the &lt;code class="docutils literal"&gt;install&lt;/code&gt; step is invoked, the the output folder is copied as-is to the target folder, which in this case is again a local folder (&lt;code class="docutils literal"&gt;oxpy_mwe&lt;/code&gt;). There, if the correct path is given in the &lt;code class="docutils literal"&gt;wheel.packages&lt;/code&gt; option of &lt;code class="docutils literal"&gt;pyproject.toml&lt;/code&gt;, scikit-build-core will be able to find the package and use it to copy it to the correct Python’s &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;site-packages&lt;/span&gt;&lt;/code&gt; folder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For the CMAKE method, we copy the README and the correct &lt;code class="docutils literal"&gt;pyproject.toml&lt;/code&gt; (the one stored in the &lt;code class="docutils literal"&gt;src/oxpy_mwe&lt;/code&gt; folder) to the target folder, and then install the package directly with &lt;code class="docutils literal"&gt;pip&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key point here is that we use slightly different &lt;code class="docutils literal"&gt;pyproject.toml&lt;/code&gt; files for the PIP and CMAKE methods, as described below.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="pyproject-toml"&gt;
&lt;h4&gt;pyproject.toml&lt;/h4&gt;
&lt;p&gt;The &lt;code class="docutils literal"&gt;pyproject.toml&lt;/code&gt; file for the PIP method is as follows:&lt;/p&gt;
&lt;pre class="code toml literal-block"&gt;&lt;code&gt;&lt;span class="k"&gt;[build-system]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;s&gt;"scikit-build-core&amp;gt;=0.9"&lt;/s&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"setuptools_scm&amp;gt;=8"&lt;/s&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"pybind11&amp;gt;=2.10"&lt;/s&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;build-backend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"scikit_build_core.build"&lt;/s&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[project]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"oxpy_mwe"&lt;/s&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dynamic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;s&gt;"version"&lt;/s&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[...]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[tool.scikit-build]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"scikit_build_core.metadata.setuptools_scm"&lt;/s&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;wheel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;s&gt;"oxpy_mwe"&lt;/s&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[tool.setuptools_scm]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;version_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"src/oxpy_mwe/_version.py"&lt;/s&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;fallback_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"unknown"&lt;/s&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;local_scheme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"no-local-version"&lt;/s&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here I’m omitting standard project-related fields (such as &lt;code class="docutils literal"&gt;classifiers&lt;/code&gt;, &lt;code class="docutils literal"&gt;authors&lt;/code&gt;, &lt;em&gt;etc&lt;/em&gt;). Here we use &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;scikit-build-core&lt;/span&gt;&lt;/code&gt; as the build backend, and tell it to look for the package in the &lt;code class="docutils literal"&gt;oxpy_mwe&lt;/code&gt; folder. If this is not specified, omitted or if the path is wrong, &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;scikit-build-core&lt;/span&gt;&lt;/code&gt; will be happy to consider &lt;code class="docutils literal"&gt;src/oxpy_mwe&lt;/code&gt; as the package folder, and silently copy that to the &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;site-packages&lt;/span&gt;&lt;/code&gt; folder. I believe that this happens because there is a &lt;code class="docutils literal"&gt;src/oxpy_mwe/__init__.py&lt;/code&gt; file, and that, according to &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;scikit-build-core&lt;/span&gt;&lt;/code&gt;, makes the folder a Python package.&lt;/p&gt;
&lt;p&gt;The last lines tell &lt;code class="docutils literal"&gt;setuptools_scm&lt;/code&gt; to write a version file that will be copied to the output folder by CMake. The &lt;code class="docutils literal"&gt;local_scheme = &lt;span class="pre"&gt;"no-local-version"&lt;/span&gt;&lt;/code&gt; makes sure that no dates or times are appended to the version string, which would, very confusingly, generate mismatches when installing the package with pip.&lt;/p&gt;
&lt;p&gt;For CMAKE, the &lt;code class="docutils literal"&gt;pyproject.toml&lt;/code&gt; file is copied to the right folder at runtime by CMake starting from the following &lt;code class="docutils literal"&gt;/src/oxpy_mwe/pyproject_cmake.toml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class="code toml literal-block"&gt;&lt;code&gt;&lt;span class="k"&gt;[build-system]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;requires&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;s&gt;"scikit-build-core&amp;gt;=0.9"&lt;/s&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"setuptools_scm&amp;gt;=8"&lt;/s&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;build-backend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"setuptools.build_meta"&lt;/s&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[...]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[tool.setuptools.package-data]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;oxpy_mwe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;s&gt;"*.so"&lt;/s&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;[tool.setuptools_scm]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;version_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"oxpy_mwe/_version.py"&lt;/s&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;fallback_version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"0.0.0"&lt;/s&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;local_scheme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"no-local-version"&lt;/s&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;s&gt;"${CMAKE_SOURCE_DIR}"&lt;/s&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, I omit the project metadata in order to focus on the important content. Here we use &lt;code class="docutils literal"&gt;setuptools.build_meta&lt;/code&gt; as the build backend, and tell it that the compiled extension (&lt;code class="docutils literal"&gt;*.so&lt;/code&gt;) is part of the package. Then, we again use &lt;code class="docutils literal"&gt;setuptools_scm&lt;/code&gt; to write a version file, but in this case we have to point it to the right root folder, where it can find the SCM (&lt;em&gt;i.e.&lt;/em&gt; &lt;code class="docutils literal"&gt;git&lt;/code&gt;) metadata. We do this with the option &lt;code class="docutils literal"&gt;root&lt;/code&gt;, which is populated by CMake when it copies the file to the output folder. Note that in this case the version file is put directly into the output folder (since the &lt;code class="docutils literal"&gt;pip install .&lt;/code&gt; command is invoked from the &lt;code class="docutils literal"&gt;/build/python&lt;/code&gt;, where CMake has put the &lt;code class="docutils literal"&gt;pyproject.toml&lt;/code&gt; file).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="continuous-integration"&gt;
&lt;h4&gt;Continuous integration&lt;/h4&gt;
&lt;p&gt;As a last note, when using GitHub Actions, make sure the workflow checks out the full repository with all tags so that &lt;code class="docutils literal"&gt;setuptools_scm&lt;/code&gt; can detect the correct version:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;code&gt;- uses: actions/checkout@v4
  with:
    fetch-depth: 0
    fetch-tags: true
&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;/main&gt;</description><category>C++</category><category>cmake</category><category>pybind</category><category>python</category><category>scikit-core</category><guid>https://www.roma1.infn.it/~rovigatl/posts/pybind_cmake/</guid><pubDate>Wed, 05 Nov 2025 15:00:48 GMT</pubDate></item><item><title>reveal.js presentations</title><link>https://www.roma1.infn.it/~rovigatl/posts/revealjs-presentations/</link><dc:creator>Lorenzo Rovigatti</dc:creator><description>&lt;main&gt;


&lt;nav class="contents" id="table-of-contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;Table of Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/revealjs-presentations/#the-problem" id="toc-entry-1"&gt;The problem&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/revealjs-presentations/#the-strategy" id="toc-entry-2"&gt;The strategy&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/revealjs-presentations/#the-template-file" id="toc-entry-3"&gt;The template file&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/revealjs-presentations/#the-presentation-page-file" id="toc-entry-4"&gt;The presentation page file&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="the-problem"&gt;
&lt;h3&gt;The problem&lt;/h3&gt;
&lt;p&gt;A few years ago I started using &lt;a class="reference external" href="https://revealjs.com/"&gt;reveal.js&lt;/a&gt; to prepare my scientific talks and I never looked back since then. Now I use it also for the &lt;a href="https://www.roma1.infn.it/~rovigatl/labcalc/"&gt;programming class&lt;/a&gt; I teach to first-year students!&lt;/p&gt;
&lt;p&gt;The only thing I hate about reveal.js is its PDF-exporting features: perhaps it is my fault (although I have &lt;a class="reference external" href="https://revealjs.com/pdf-export/"&gt;rtfm’d&lt;/a&gt;), but I never managed to generate a PDF that faithfully reproduced my presentation. And of course PDFs don’t support movies (in a portable and reliable manner).&lt;/p&gt;
&lt;p&gt;When I decided to use Nikola to build my website I thought that I could maybe find a way of making my presentations available online as HTML pages. I didn’t find any resources on the topic online, but I used what I learned about Nikola in those last weeks and I hacked up my own solution, which I’m going to describe here.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-strategy"&gt;
&lt;h3&gt;The strategy&lt;/h3&gt;
&lt;p&gt;The basic idea is to have a custom template page &lt;code class="docutils literal"&gt;reveal.tmpl&lt;/code&gt; that&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;loads the necessary reveal (and possibly custom) javascript and css files required to display the presentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sets up the HTML bits that will contain the markdown/HTML code.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The (markdown/HTML) content of each presentation is then copied in an HTML file containing the special &lt;code class="docutils literal"&gt;.. template: reveal.tmpl&lt;/code&gt; metadata overriding the default template file for pages.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-template-file"&gt;
&lt;h3&gt;The template file&lt;/h3&gt;
&lt;p&gt;If you are curious you can look at (and download) the custom template I have written &lt;a href="https://www.roma1.infn.it/~rovigatl/listings/reveal.tmpl.html"&gt;here&lt;/a&gt;, which depends on the theme I’m using (a modified version of &lt;a class="reference external" href="https://themes.getnikola.com/v8/bnw/"&gt;bnw&lt;/a&gt;) to output the first part of the HTML page. Here I will describe the most important parts of the template.&lt;/p&gt;
&lt;p&gt;I decided to use reveal.js version 4.1.2 and load the relevant file through a CDN, but the same procedure should work with any version and/or files stored elsewhere.&lt;/p&gt;
&lt;p&gt;We start with loading the CSS files in the &lt;code class="docutils literal"&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element:&lt;/p&gt;
&lt;pre class="code html literal-block"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"stylesheet"&lt;/s&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.1.2/reset.min.css"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"stylesheet"&lt;/s&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.1.2/reveal.min.css"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"stylesheet"&lt;/s&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"/assets/css/slides.css"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here &lt;code class="docutils literal"&gt;slides.css&lt;/code&gt; is the file containing my custom CSS styles.&lt;/p&gt;
&lt;p&gt;Within the &lt;code class="docutils literal"&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element there is the reveal.js HTML boilerplate, where &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;${post.text()}&lt;/span&gt;&lt;/code&gt; will be replaced by each presentation’s content:&lt;/p&gt;
&lt;pre class="code html literal-block"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"reveal"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"slides"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            % if post.meta('is_html') and post.meta('is_html') == 'true':
            ${post.text()}
            % else:
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;data-markdown&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;data-template&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    ${post.text()}
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            % endif
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I tend to write my teaching slides with markdown and my talks directly in HTML. With the structure above, an HTML presentation should contain a &lt;code class="docutils literal"&gt;.. is_html: true&lt;/code&gt; metadata to be correctly displayed.&lt;/p&gt;
&lt;p&gt;Finally, just before &lt;code class="docutils literal"&gt;&amp;lt;/body&amp;gt;&lt;/code&gt; there is the import and initialisation of reveal.js’s javascript machinery:&lt;/p&gt;
&lt;pre class="code html literal-block"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.1.2/reveal.js"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.1.2/plugin/notes/notes.min.js"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.1.2/plugin/markdown/markdown.min.js"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.1.2/plugin/highlight/highlight.min.js"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.1.2/plugin/math/math.min.js"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.1.2/plugin/zoom/zoom.min.js"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Reveal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    
    &lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;mathjax&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'https://cdn.jsdelivr.net/gh/mathjax/mathjax@2.7.8/MathJax.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TeX-AMS_HTML-full'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    
    &lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nx"&gt;smartypants&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    
    &lt;/span&gt;&lt;span class="nx"&gt;slideNumber&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'c/t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;showSlideNumber&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'all'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RevealMarkdown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RevealHighlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RevealNotes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RevealMath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RevealZoom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;aside class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Tip&lt;/p&gt;
&lt;p&gt;I have added the following link to help the user go back to the “normal” part of the website&lt;/p&gt;
&lt;pre class="code html literal-block"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"back_button"&lt;/s&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"#"&lt;/s&gt; &lt;span class="na"&gt;onclick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;s&gt;"history.back()"&lt;/s&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The link is fixed in the topmost left corner and is styled as an arrow with the following CSS code:&lt;/p&gt;
&lt;pre class="code css literal-block"&gt;&lt;code&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;back_button&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;back_button&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"\f060"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'fontawesome'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/aside&gt;
&lt;/section&gt;
&lt;section id="the-presentation-page-file"&gt;
&lt;h3&gt;The presentation page file&lt;/h3&gt;
&lt;p&gt;Since we use HTML files to store presentations styled with reveal.js, we first need to make sure that HTML pages are enabled in the &lt;code class="docutils literal"&gt;conf.py&lt;/code&gt; file. The relevant part of my configuration is as follows:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;&lt;code&gt;&lt;span class="n"&gt;PAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pages/*.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"page.tmpl"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pages/*.rst"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"page.tmpl"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"pages/*.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"page.tmpl"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then create a new HTML page (with &lt;code class="docutils literal"&gt;nikola new_page &lt;span class="pre"&gt;-f&lt;/span&gt; html&lt;/code&gt; for instance) and update its metadata to something like this:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;code&gt;&amp;lt;!--
.. title: My fancy seminar
.. slug: fancy_seminar
.. date: 2023-08-08 13:12:44 UTC+02:00
.. tags: 
.. category: seminars
.. link: 
.. description: 
.. type: text
.. template: reveal.tmpl
.. has_math: true
--&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where the most important bit is &lt;code class="docutils literal"&gt;.. template: reveal.tmpl&lt;/code&gt;, where we override the default template.&lt;/p&gt;
&lt;aside class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Tip&lt;/p&gt;
&lt;p&gt;Don’t forget to set &lt;code class="docutils literal"&gt;.. is_html: true&lt;/code&gt; if the presentation is not written in markdown (&lt;em&gt;i.e&lt;/em&gt;. if you use &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;lt;section&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;&lt;/code&gt; tags for each slide)&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;The rest of the file should contain the presentation text. Here is an example:&lt;/p&gt;
&lt;pre class="code markdown literal-block"&gt;&lt;code&gt;&lt;span class="gh"&gt;# This is a fancy seminar&lt;/span&gt;

&lt;span class="gu"&gt;## by Lorenzo Rovigatti&lt;/span&gt;

---

&lt;span class="gh"&gt;# Introduction&lt;/span&gt;

&amp;lt;img src="fancy_image.png"&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;/main&gt;</description><category>nikola</category><guid>https://www.roma1.infn.it/~rovigatl/posts/revealjs-presentations/</guid><pubDate>Tue, 08 Aug 2023 12:12:36 GMT</pubDate></item><item><title>Nikola tips</title><link>https://www.roma1.infn.it/~rovigatl/posts/nikola/</link><dc:creator>Lorenzo Rovigatti</dc:creator><description>&lt;main&gt;


&lt;nav class="contents" id="table-of-contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;Table of Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/nikola/#templates" id="toc-entry-1"&gt;Templates&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/nikola/#math-support" id="toc-entry-2"&gt;Math support&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/nikola/#images" id="toc-entry-3"&gt;Images&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.roma1.infn.it/~rovigatl/posts/nikola/#relative-links" id="toc-entry-4"&gt;Relative links&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;I use an &lt;a class="reference external" href="https://www.anaconda.com/"&gt;anaconda&lt;/a&gt; environment (unoriginally called &lt;code class="docutils literal"&gt;nikola&lt;/code&gt;) to build my site, which means that nikola’s files are stored in &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;/path/to/anaconda/envs/nikola/lib/python3.XX/sites-packages/nikola&lt;/span&gt;&lt;/code&gt;, where &lt;code class="docutils literal"&gt;XX&lt;/code&gt; is the environment’s python version (10 in my case). This is useful whenever I need to inspect some source file to understand what’s going on under the hood, or look at how things are implemented (mostly in template files).&lt;/p&gt;
&lt;p&gt;In the following, paths starting with &lt;code class="docutils literal"&gt;NIKOLA&lt;/code&gt; are relative to the foregoing nikola’s source dir, while paths starting with &lt;code class="docutils literal"&gt;/&lt;/code&gt; are relative to the website’s source root folder.&lt;/p&gt;
&lt;/aside&gt;
&lt;section id="templates"&gt;
&lt;h3&gt;Templates&lt;/h3&gt;
&lt;p&gt;The theme you are using may lack a template file for a specific page. In my case, &lt;code class="docutils literal"&gt;listing.tmpl&lt;/code&gt; was missing. Therefore I copied the &lt;code class="docutils literal"&gt;NIKOLA/data/themes/base/templates/listing.tmpl&lt;/code&gt; file over to &lt;code class="docutils literal"&gt;/themes/my_theme/templates&lt;/code&gt; and edited it to make its structure similar to my theme’s other &lt;code class="docutils literal"&gt;tmpl&lt;/code&gt; files.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="math-support"&gt;
&lt;h3&gt;Math support&lt;/h3&gt;
&lt;p&gt;With Nikola you can enable math support for a specific page by adding &lt;code class="docutils literal"&gt;has_math: true&lt;/code&gt; to its metadata. However, if you use long equations and want to support devices with small screens, remember to add &lt;code class="docutils literal"&gt;&lt;span class="pre"&gt;"HTML-CSS":&lt;/span&gt; { linebreaks: { automatic: true } },&lt;/code&gt; to MathJax’s config in &lt;code class="docutils literal"&gt;conf.py&lt;/code&gt;.&lt;/p&gt;
&lt;aside class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;Warning&lt;/p&gt;
&lt;p&gt;As of now (July 2023), automatic line breaks &lt;a class="reference external" href="https://docs.mathjax.org/en/latest/output/linebreaks.html"&gt;are not supported in MathJax 3&lt;/a&gt;.&lt;/p&gt;
&lt;/aside&gt;
&lt;/section&gt;
&lt;section id="images"&gt;
&lt;h3&gt;Images&lt;/h3&gt;
&lt;p&gt;By default Nikola creates thumbnails for each image you put into the &lt;code class="docutils literal"&gt;images&lt;/code&gt; folder (or subfolders). If you don’t need thumbnails and want to save compiling time and disk space you can put everything in &lt;code class="docutils literal"&gt;files/images&lt;/code&gt; instead. This has the benefit of keeping all the paths valid, since in either case images will be placed in &lt;code class="docutils literal"&gt;/images/path/to/image.jpg&lt;/code&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="relative-links"&gt;
&lt;h3&gt;Relative links&lt;/h3&gt;
&lt;p&gt;I wrote a javascript function that takes paths to data files stored in a given folder as arguments. As always, paths can be relative to the current page or absolute. If one chooses the former approach, if the page is moved somewhere else the links may break, which is why I tend to prefer absolute links. Often the best of way of specifying absolute links on a website is to have something like &lt;code class="docutils literal"&gt;/path/to/dest&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As far as I understand, nikola translates all absolute links specified in this way to relative links, so that these links work regardless of the root URL (&lt;em&gt;i.e.&lt;/em&gt; if the website is browsed locally, if it’s deployed on a first-level domain or as a subfolder). Unfortunately, nikola doesn’t do the same to paths specified not in link (&lt;em&gt;e.g.&lt;/em&gt; in javascript code, as in my case). As a result, I needed a way of translating these absolute paths to relative paths. In order to do so I tried to use &lt;a class="reference external" href="https://getnikola.com/extending.html#template-based-shortcodes"&gt;template-based shortcodes&lt;/a&gt; and put the following code in the &lt;code class="docutils literal"&gt;/shortcodes/rel_url.tmpl&lt;/code&gt; path:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;code&gt;${rel_link(post.permalink(), dst)} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shortcode calls nikola’s &lt;a class="reference external" href="https://docs.getnikola.com/en/latest/nikola.html#nikola.nikola.Nikola.rel_link"&gt;rel_link&lt;/a&gt; method, passing it the current post’s permalink and the destination path specified by the user, and can be used like this in a page or post:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'../../my_file/file.dat'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, the snippet above will result in something like this:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'../../my_file/file.dat
'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;since, as far as I understand, nikola adds a trailing new line to the output of template-based shortcodes. A workaround would be to surround the shortcode’s code with single (or double) quotes, making the plugin less general and more prone to subtle bugs.&lt;/p&gt;
&lt;p&gt;The solution I found was to add a “proper” plugin, since nikola apparently uses its output &lt;em&gt;verbatim&lt;/em&gt;, without adding newlines or other whitespace. In order to do so I simply created a &lt;code class="docutils literal"&gt;/plugins/rel_url&lt;/code&gt; folder and added these two files in it:&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;rel_url.plugin&lt;/code&gt;&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;code&gt;[Core]
Name = rel_url
Module = rel_url

[Nikola]
PluginCategory = Shortcode

[Documentation]
Author = Lorenzo Rovigatti
Version = 0.1
Website = https://www.roma1.infn.it/~rovigatl/
Description = Generate a relative URL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;rel_url.py&lt;/code&gt;&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;nikola.plugin_categories&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ShortcodePlugin&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RelUrlShortcodePlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ShortcodePlugin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;"""Return a relative URL."""&lt;/span&gt;
    
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rel_url"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rel_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permalink&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;/main&gt;</description><category>nikola</category><category>tips</category><guid>https://www.roma1.infn.it/~rovigatl/posts/nikola/</guid><pubDate>Sun, 02 Jul 2023 19:41:48 GMT</pubDate></item></channel></rss>