Documentation guidelines#
A brief set-of-rules for the documentation.
Note
The old wiki uses dokuwiki. When importing text from there, consider the automated options that are provided in this page to convert it to reST.
Importing from DokuWiki to Sphinx#
Use the following command to import a DokuWiki page (old wiki.analog.com):
pandoc imported.txt -f dokuwiki -t rst --columns=80 -s -o imported.rst --list-tables
The list-tables
parameter requires pandoc-types >= 1.23, included in any
recent pandoc release;
if it is not an option, you shall remove it and export in the grid table
format (see Tables for more information).
After converting, update it to better conform with the guidelines below, and make sure to use our directives and roles, for example, the Git role.
To speed thing up, combine with wget:
wikifile=resources/eval/user-guides/adrv9009/adrv9009
outfile=output.rst
wget -O - https://wiki.analog.com/$wikifile?do=export_raw --no-verbose | \
pandoc -f dokuwiki -t rst --columns=80 -s -o $outfile --list-tables
Also, consider recording macros in your favorite text editor to automate repetitive steps.
Indentation#
Directives are indented with 3 space, which is Sphinx’s default. At code directives, the code keeps its original indentation (e.g. 2 spaces for Verilog code), but is offset by 3 spaces at the beginning of every line, to instruct Sphinx the beginning and end of the code directive.
Table of contents#
The relation between pages are created with the toctree
directive,
which allows to generate the table of contents and navigation bars.
Each page may have only one toctree, since they are the equivalent of the volumes of a book, and it does not make sense to have multiple “volumes” at the repository level.
The only exception is the ADI System Level Documentation repository, which indeed contains various types of documentation (eval-boards, university program, Linux drivers, etc.).
The toctree
directive has the following format:
.. toctree::
:maxdepth: <depth>
Custom title <path/to/page>
For pages with shorter titles, such as libraries, the label is inherited from the page itself, for example:
.. toctree::
library/axi_dmac/index
library/spi_engine/index
And for pages with long titles, such as projects, overwrite the full title with a custom title, e.g:
.. toctree::
AD7616-SDZ <projects/ad7616_sdz/index>
This way, only “AD7616-SDZ” will be displayed in the page navigation bar instead of “AD7616-SDZ HDL project”.
Also, it is recommended to wrap the toctree in a “Contents” section:
Contents
========
.. toctree::
some_page
For extensive documentation with different topics, it makes sense to filter
the toctree based on the current topic/toctree title.
This is possible by setting the environment variable ADOC_FILTER_TOCTREE
to
1
.
Alternatively, setting filter_toctree
on conf.py
has higher precedence
than ADOC_FILTER_TOCTREE
.
And is supposed to be used alongside the topic
field at lut.py
to
preserve high level links for each topic.
Enable this environment variable only on the release build, since writing pages with it enabled may be obnoxious and confusing prior the final structure/location of them.
Versioning#
To avoid having the version set in multiple places or having to tweak conf.py
to obtain it from somewhere else, continuous integration can set the environment
variable ADOC_DOC_VERSION
to set the version value.
Still, the version
value on conf.py
has higher precedence, and
ADOC_DOC_VERSION
will be ignored if the variable is already set.
The CI, in general, should set ADOC_DOC_VERSION
as the current checkout branch
in the pipeline (e.g. main
, v1.0.0
).
Tip
If creating a branch or PR output, consider using GitHub short reference
${{ github.ref_name }}
.
If both environment variable and version
on conf.py
are unset, it defaults
to an empty string.
Also, set ADOC_TARGET_DEPTH
to match the final destination depth, for example,
if the target directory is:
./:
0
or unset./v2.2:
1
./prs/1234:
2
./staging/user/branch:
3
Exporting to PDF#
The whole documentation can be exported to a PDF document for a more compact
format. This is done by setting the environment variable called
ADOC_MEDIA_PRINT
to 1 (default it’s 0) and building the documentation using
this command:
user@analog:~/doctools/docs$ sphinx-build -b pdf . _build/pdfbuild
In the output folder, you’ll find a PDF document named after the repository (e.g. Doctools.pdf). This document includes an auto-generated cover, followed by the remaining pages. Note that an HTML build of the documentation is not required for the PDF build.
Warning
The enviroment variable ADOC_MEDIA_PRINT
should be set to 0 when building
the HTML pages of documentation. If not set, some components of the pages
may not render properly.
Local references#
References to labels have the format :ref:`context topic`
, e.g.
:ref:`role git`
renders as Git role.
Labels are created for any content with the syntax (dot-dot underscore<label>two-dots):
.. _context topic:
Hint
Add labels to any content that may be linked, locally or externally.
References to docs have the format :doc:`path/to/doc`
, e.g.
:doc:`docs_guidelines`
for docs_guidelines.rst.
Attention
Do not break reference roles between lines! Even though Sphinx allows breaking line inside the reference role, it makes pattern matching hard.
Prefer hyphen separation -
over undeline _
for the “title” section,
and always lower case,
for example
my_code control-interface
instead of MY_CODE Control_Interface
.
Numbered references#
References can be numbered by using numref, for example, “see Figure 1”.
To use this feature, enable on the conf.py:
numfig = True
To customize the format, set numfig_format
:
numfig_format = {'figure': 'Figure %s',
'table': 'Table %s',
'code-block': 'Listing %s',
'section': 'Section %s'}
Tip
By default enumeration is global, so if the toctree is not numbered to divide the pages in numbered sections (e.g. Figure 3.4.4), the numbering will “propagate” across page which may be counter-intuitive.
To have the numbering reset at every page, add numfig_per_doc = True
to
conf.py.
External references#
External references to other Sphinx documentation are created using the built-in
sphinx.ext.intersphinx
extension.
To setup in-organization references read the section below, and for third-party docs, the section that follows.
:external+inv:ref:`label`
, where inv
is a mapped source,
for example, :external+hdl:ref:`spi_engine control-interface`
.:external+inv:doc:`label`
, where inv
is a mapped source,
for example, :external+hdl:doc:`library/spi_engine/index`
.As the other roles, it is possible to customize the text, e.g.
:external+hdl:ref:`Custom text <spi_engine control-interface>`
.
Tip
Pay attention to the log output, since a warnings is thrown for each reference not found.
External references work with any kind of references, such as ref, doc, envvar, token, term, numref and keyword.
:ref-inv:`label`
(equivalent to :external+inv:ref:`label`
),
but is discouraged.Note
Sphinx 8 allows the syntax :ref:`<inv>:label`
,
which allows local references to have higher precedence than external
refs, useful for generating custom docs like user guides.
However, since some users may require Sphinx 7, use the former syntax,
and let adoc patch it when necessary.
To show all links of an InterSphinx mapping file, use the built-in tool:
python3 -m sphinx.ext.intersphinx https://analogdevicesinc.github.io/hdl/objects.inv
In organization reference#
To create references to Sphinx docs inside the organization add the repositories of interest to the conf.py file with the following format:
interref_repos = [external...]
For example:
interref_repos = ['hdl', 'no-OS', 'pyadi-iio/main']
Notice that in the example main
suffixes pyadi-iio
, this means that will
look for the build at path main
of this repo instead of at root.
This can be used to target a specific version, if the target repository stores
multiple, for example, v1.1
, more about that Versioned.
Tip
For even more freedom, you can setup with an explicit path as an Outside organization Sphinx reference.
It is possible to customize the target URL with the interref_uri
config or
ADOC_INTERREF_URI
environment variables.
The default value is https://analogdevicesinc.github.io/ and can be set to a
local path like ../../.
Beyond the main target dictated by interref_uri,
by setting the config interref_local
as true, a secondary target is inferred
foreseeing a local copy of the target external documentation alongside the
current repository:
/data/work
├─my-repo-0/doc/sources
│
├─my-repo-1/docs
│
└─my-repo-2/doc
The correct relative paths are resolved looking into the lut.py
.
Outside organization Sphinx reference#
To create references to third-party Sphinx documentations, add the mappings to to the conf.py file with the following format:
intersphinx_mapping = {
'<name>': ('<path/to/external>', None)
}
For example:
intersphinx_mapping = {
'sphinx': ('https://www.sphinx-doc.org/en/master', None)
}
Text width#
Each line must be less than 80 columns wide.
You can use the fold
command to break the lines of the imported text
while respecting word-breaks:
cat imported.txt | fold -sw 80 > imported.rst
The header divider “---
” shall be either 80 characters wide or end at the
title character, that means, this is also valid:
My title
========
Tables#
Prefer list-tables and imported csv-tables (using the file option), because they are faster to create, easier to maintain and the 80 column-width rule can be respected with list-tables.
Only use grid tables if strictly necessary, since they are hard to update.
Lists#
Unordered lists use *
or -
and ordered lists #.
.
Child items must be aligned with the first letter of the parent item, that means, 2 spaces for unordered list and 3 spaces for ordered lists, for example:
#. Parent ordered item.
* Child unordeded item.
#. Child ordered item.
#. Child ordered item.
Renders as:
Parent numbered item.
Child unordered item.
Child ordered item.
Child ordered item.
Code#
Prefer code-blocks to code directives, because code-blocks have more options, such as showing line numbers and emphasizing lines.
For example,
.. code-block:: python
:linenos:
:emphasize-lines: 2
def hello_world():
string = "Hello world"
print(string)
Renders as
1def hello_world():
2 string = "Hello world"
3 print(string)
Images#
Prefer the SVG format for images, and save it as Optimized SVG in inkscape to use less space.
Store them in a hierarchically, do not use images
subdirectories.
The idea is to have simpler relative paths, for example, e.g.:
.. image:: ad2234_sdz_schematic.svg
Instead of over complicated paths like:
.. image:: ../../project/images/ad2234_sdz/ad2234_sdz_schematic.svg
In general, this avoids dangling artifacts and keeps the documentation simple.
Git Large File Storage#
Where applicable, Git Large File Storage (LFS) is used to replace large files with text pointers inside Git, reducing cloning time.
To setup, install from your package manager and init:
apt install git-lfs
git lfs install
The files that will use Git LFS are tracked at .gitattributes
, to add new
files use a pattern at the repo root, for example:
git lfs track *.jpg
Or edit .gitattributes
directly.
Third-party directives and roles#
Third-party tools are used to expand Sphinx functionality, if you haven’t already, do:
pip install -r requirements.txt
Custom directives and roles#
To expand Sphinx functionality beyond existing tools, custom directives and roles have been written, which are located in the docs/extensions folder. Extensions are straight forward to create, if some functionality is missing, consider requesting or creating one.
Note
Link-like roles use the :role:`text <link>`
synthax, like external
links, but without the undescore in the end.
Color role#
To print text in red or green, use :red:`text`
and :green:`text`
.
Link roles#
The link roles are a group of roles defined by adi_links.py
.
The validate_links
global option is used to validate each link during build.
These links are not managed, that means, only links from changed files are checked.
You can run a build with it set to False, then touch the desired files to check
the links of only these files.
Git role#
The Git role allows to create links to the Git repository with a shorter syntax.
The role syntax is :git-repo:`text <type+branch:path>`
, for example:
:git-hdl:`main:docs/user_guide/docs_guidelines.rst`
renders as docs/user_guide/docs_guidelines.rst.:git-hdl:`Guidelines <docs/user_guide/docs_guidelines.rst>`
renders as Guidelines.:git-wiki-scripts:`raw+linux/build_zynq_kernel_image.sh
`` renders as linux/build_zynq_kernel_image.sh.
Important
The repository name is case sensitive.
When the branch field is not present, it will be filled with the current branch.
It is recommended to not provide this field when it is a link to its own repository,
because it is useful to auto-fill it for documentation releases
(e.g. hdl_2023_r2
).
A scenario where it is recommended to provide the branch is when linking others
repositories.
They type field is also optional and the values are:
gui: To view rendered on the Git server web GUI [default].
raw: To download/view as raw.
{}: Any other Git server web GUI link, e.g.
:git-hdl:`releases+`
. The last character must be+
, since filenames/path may contain this character also.
The text field is optional and will be filled with the full path.
Finally, you can do :git-repo:`/`
for a link to the root of the
repository with pretty naming, for example, :git-hdl:`/`
is rendered
as ADI HDL repository.
ADI role#
The adi role creates links for a webpage to the Analog Devices Inc. website.
The role syntax is :adi:`text <webpage>`
, for example,
:adi:`AD7175-2 <ad7175-2>`
.
Since links are case insensitive, you can also reduce it to
:adi:`AD7175-2`
, when webpage is the same as text and will render
as AD7175-2.
Dokuwiki role#
The dokuwiki role creates links to the Analog Devices Inc. wiki website.
The role syntax is :dokuwiki:`text <path>`
, for example,
:dokuwiki:`pulsar-adc-pmods <resources/eval/user-guides/circuits-from-the-lab/pulsar-adc-pmods>`
gets rendered as
pulsar-adc-pmods.
For intentional deprecated links, add the deprecated
qualifier, e.g.:
:dokuwiki+deprecated:`ADS-B Airplane Tracking Example <resources/tools-software/linux-software/libiio/clients/adsb_example>`
gets rendered as
ADS-B Airplane Tracking Example.
The sole intend of this qualifier is to distinct pending import pages from won’t
import pages.
EngineerZone role#
The ez role creates links to the Analog Devices Inc. EngineerZone support website.
The role syntax is :ez:`community`
, for example, :ez:`fpga`
gets rendered as EngineerZone.
For Linux Software Drivers, it is :ez:`linux-software-drivers`
.
For Microcontroller no-OS Drivers it is :ez:`microcontroller-no-os-drivers`
.
Vendor role#
The vendor role creates links to the vendor’s website.
The role syntax is :vendor:`text <path>`
, for example,
:xilinx:`Zynq-7000 SoC Overview <support/documentation/data_sheets/ds190-Zynq-7000-Overview.pdf>`
gets rendered
Zynq-7000 SoC Overview.
The text parameter is optional, if absent, the file name will be used as the text,
for example,
:intel:`content/www/us/en/docs/programmable/683780/22-4/general-purpose-i-o-overview.html`
gets rendered
general-purpose-i-o-overview.html
(not very readable).
Supported vendors are: xilinx
(AMD Xilinx), intel
(Intel Altera) and
mw
(MathWorks).
Clear content directive#
A simple directive to clear the content, forcing any following content to be moved below any preceding content. It is useful when working with images aligned/float left/right and wants to ensure the next section does not also gets “squashed”.
.. clear-content::
:side: [both,left,right]
:break:
It can clear content to it’s left
, right
or both
sides.
By default, it clear both
sides.
With the break
option, it will break the page when generating a PDF
(behaves similar to LaTeX cleardoublepage).
Shell directive#
The shell directive allows to embed shell code in a standard way.
.. shell:: [bash,sh,zsh,ps1]
:user: <user>
:group: <group>
:caption: <caption>
:show-user:
:no-path:
/path_absolute
~path_relative_to_home
$command
output
That means, each line is prefixed by character to:
$
: bash commands.(one space): command output.
#
: bash comments/
: set absolute working directory (cygpath-formatted for ps1).~
: set relative to “home” working directory (cygpath-formatted for ps1).
Anything that does not match the previous characters will default to output print, but please be careful, since you may accidentally mark a working directory or command, if not identing the output by one space.
The bash type defaults to bash
, user to user
, group to analog
and the working directory as “doesn’t matter” (hidden), so, for
example:
.. shell::
:caption: iio_reg help
$iio_reg -h
Usage:
iio_reg <device> <register> [<value>]
Renders as:
user@analog:~$
iio_reg -h
Usage:
iio_reg <device> <register> [<value>]
Insight
To make it super easy for the user to copy only the command, the current directory and output cannot be selected.
To show the user and user group, add the :show-user:
flag.
For Windows, set bash type as ps1
(PowerShell), for example:
.. shell:: ps1
:user: Analog
/e/MyData
$cd ~/Documents
$ls
Mode LastWriteTime Name
---- ------------- ----
d---- 6/14/2024 10:30 AM ImportantFiles
d---- 6/14/2024 10:30 AM LessImportantFiles
$cd ..\Other\Folder
$echo HelloWindows
HelloWindows
Renders as:
E:\MyData>
cd ~/Documents
C:\Users\Analog\Documents>
ls
Mode LastWriteTime Name
---- ------------- ----
d---- 6/14/2024 10:30 AM ImportantFiles
d---- 6/14/2024 10:30 AM LessImportantFiles
C:\Users\Analog\Documents>
cd ..\Other\Folder
C:\Users\Analog\Other\Folder>
echo HelloWindows
HelloWindows
To make things more interesting, basic $cd
commands change the working
directory accordingly, for example:
.. shell::
$cd /sys/bus/iio/devices/
$ls
iio:device0 iio:device3 iio:device2 iio:device3 iio:device4 iio:device5 iio:device6
$cd iio:device3
$ls -al
total 0
drwxr-xr-x 3 root root 0 May 16 14:21 .
-rw-rw-rw- 1 root root 4096 May 16 14:22 calibrate
-rw-rw-rw- 1 root root 4096 May 16 14:22 calibrate_frm_en
Renders as:
~$
cd /sys/bus/iio/devices/
/sys/bus/iio/devices$
ls
iio:device0 iio:device3 iio:device2 iio:device3 iio:device4 iio:device5 iio:device6
/sys/bus/iio/devices$
cd iio:device3
/sys/bus/iio/devices/iio:device3$
ls -al
total 0
drwxr-xr-x 3 root root 0 May 16 14:21 .
-rw-rw-rw- 1 root root 4096 May 16 14:22 calibrate
-rw-rw-rw- 1 root root 4096 May 16 14:22 calibrate_frm_en
Finally, be mindful of the command legibility, break long commands and sugar coat with indent:
.. shell::
# Write the file to the storage devices
$time sudo dd \
$ if=2021-07-28-ADI-Kuiper-full.img \
$ of=/dev/mmcblk0 \
$ bs=4194304
[sudo] password for user:
0+60640 records in 0+60640 records out 7948206080 bytes (7.9 GB) copied, 571.766 s, 13.9 MB/s
real 7m54.11s user 0.29s sys 8.94s
Renders to:
~$
# Write the file to the storage device
~$
time sudo dd \
if=2021-07-28-ADI-Kuiper-full.img \
of=/dev/mmcblk0 \
bs=4194304
[sudo] password for user:
0+60640 records in 0+60640 records out 7948206080 bytes (7.9 GB) copied, 571.766 s, 13.9 MB/s
real 7m54.11s user 0.29s sys 8.94s
HDL build status directive#
The HDL build status directive gets information from a markdown formatted status table (output.md) and generates a table with the build statuses.
The directive syntax is:
.. hdl-build-status::
:file: <build_status_file>
The :path:
option is optional, in the sense that if it’s not provided, no table
is generated.
If provided, but the build status file does not exist, an error is
thrown.
Note
The :path:
option is meant to be “filled” during a CI procedure.
HDL parameters directive#
The HDL parameters directive gets information parsed from IP-XACT (component.xml) library and generates a table with the IP parameters.
Note
The IP-XACT files are generated by Vivado during the library build and not by the documentation tooling.
The directive syntax is:
.. hdl-parameters::
:path: <ip_path>
* - <parameter>
- <description>
For example:
.. hdl-parameters::
:path: library/spi_engine/spi_engine_interconnect
* - DATA_WIDTH
- Data width of the parallel SDI/SDO data interfaces.
* - NUM_OF_SDI
- Number of SDI lines on the physical SPI interface.
Descriptions in the directive have higher precedence than in the component.xml file.
The :path:
option is optional, and should not be included if the
documentation file path matches the component.xml hierarchically.
HDL interface directive#
The HDL interfaces directive gets information parsed from component.xml library and generates tables with the IP interfaces, both buses and ports.
Note
The component.xml files are generated by Vivado during the library build and not by the documentation tooling.
The directive syntax is:
.. hdl-interfaces::
:path: <ip_path>
* - <port/bus>
- <description>
For example:
.. hdl-interfaces::
:path: library/spi_engine/spi_engine_interconnect
Descriptions in the directive have higher precedence than in the component.xml file. You can provide description to a port or a bus, but not for a bus port. Ports/buses that are consecutive are squashed into a single instance to avoid repetition, for example:
{data_tx_12_p, data_tx_23_p} -> data_tx_*_p
{data_tx_12, data_tx_23} -> data_tx_*
{adc_data_i0, adc_data_i0} -> adc_data_i*
{adc_data_q0, adc_data_q0} -> adc_data_q*
{rx_phy2, rx_phy4} -> rx_phy*
To provide a description to the squashed signals/buses, write, for example,
data_tx_*
once instead of the original name of all.
Warning
Do not create new IP with signals named as _phy*
, it was added for
legacy puporses, instead suffix with _*
, e.g. mysignal_phy_4
.
The :path:
option is optional, and should not be included if the
documentation file path matches the component.xml hierarchically.
HDL component diagram directive#
The HDL component diagram directive gets information parsed from component.xml library and generates a component diagram for the IP with buses and ports information.
Note
The component.xml files are generated by Vivado during the library build and not by the documentation tooling.
The directive syntax is:
.. hdl-component-diagram::
:path: <ip_path>
For example:
.. hdl-component-diagram::
:path: library/spi_engine/spi_engine_interconnect
The :path:
option is optional, and should not be included if the
documentation file path matches the component.xml hierarchically.
Note
This directive replaces the deprecated symbolator
directive.
HDL regmap directive#
The HDL regmap directive gets information from docs/regmap/adi_regmap_*.txt files and generates tables with the register maps.
The directive syntax is:
.. hdl-regmap::
:name: <regmap_name>
:no-type-info:
For example:
.. hdl-regmap::
:name: DMAC
Note
The register map name is the title-tool, the value above ENDTITLE
in the
source file.
This directive does not support content for descriptions, since the source file already have proper descriptions.
The :name:
option is required, because the title tool does not match
the IP name and one single docs/regmap/adi_regmap_*.txt file can have more than
one register map.
The :no-type-info:
option is optional, and should not be included if it is
in the main IP documentation page. It appends an auxiliary table explaining the
register access types.
Collapsible directive#
The collapsible directive creates a collapsible/dropdown/”HTML details”.
The directive syntax is:
.. collapsible:: <label>
<content>
For example:
.. collapsible:: Python code example.
.. code:: python
print("Hello World!")
Renders as:
print("Hello World!")
Notice how you can use any Sphinx syntax, even nest other directives.
Video directive#
The video directive creates a embedded video. Currently, direct MP4 and youtube embed links are supported, but could be easily expanded to support third-party services.
The directive syntax is:
.. video:: <url>
<caption>
Always add a caption to the video, since a PDF output won’t contain the embed video, but a link to it.
For example:
.. video:: http://ftp.fau.de/fosdem/2015/devroom-software_defined_radio/iiosdr.mp4
**Linux Industrial IO framework** - Lars-Peter Clausen, Analog Devices Inc
Renders as:
And:
.. video:: https://www.youtube.com/watch?v=p_VntEwUe24
**LibIIO - A Library for Interfacing with Linux IIO Devices** - Dan Nechita, Analog Devices Inc
Renders as:
LibIIO - A Library for Interfacing with Linux IIO Devices - Dan Nechita, Analog Devices Inc
Video
LibIIO - A Library for Interfacing with Linux IIO Devices - Dan Nechita, Analog Devices Inc
ESD warning directive#
The ESD warning directive creates a ESD warning, for example:
.. esd-warning::
Renders as:
All the products described on this page include ESD (electrostatic discharge) sensitive devices. Electrostatic charges as high as 4000V readily accumulate on the human body or test equipment and can discharge without detection. Although the boards feature ESD protection circuitry, permanent damage may occur on devices subjected to high-energy electrostatic discharges. Therefore, proper ESD precautions are recommended to avoid performance degradation or loss of functionality. This includes removing static charge on external equipment, cables, or antennas before connecting to the device.
Global options for directives#
Set hide_collapsible_content
to True
to hide the collapsibles by default.
Set monolithic
to True
prefix paths with repos/<repo>.
This is meant for the System Top Documentation repository only.
Common sections#
HDL common sections#
The More information and Support sections that are present in the HDL project documentation, are actually separate pages inserted as links. They’re located at hdl/projects/common/more_information.rst and /support.rst, and cannot be referenced here because they don’t have an ID at the beginning of the page, so not to have warnings when the documentation is rendered that they’re not included in any toctree.
They are inserted like this:
.. include:: ../common/more_information.rst
.. include:: ../common/support.rst
And will be rendered as sections of the page.
Dynamic elements#
Dynamic elements refer to sections of the generated webpage that updates when
loaded online from a source of truth, in general, doctools/*.json
files;
it uses a concept similar to “react components”.
These *.json
files are generated when export_metadata
is true in the
conf.py
.
From the JavaScript side, it fetches from
{content_root}[../versioned]/../doctools/[versioned]/metadata.json
.
Note
path version
is present and set if latest
exists at
{content_root}/../doctools
and the stored version can be extracted.
The dynamic elements are:
The navigation bar at the top is updated using the
repotoc
entry indoctools/metadata.json
.A banner at the top is present/updated when the
banner
entry indoctools/metadata.json
exists.