Variant Management

Generic Hints

Warning

You can write headlines / sections in the content of few variant management directives. But you have to be careful with the correct ordering of sections in all possible output variants. Especially as Sphinx is parsing the level of headline with the occurance of underlining characters, which are not assigned to levels.

Sphinx: only Directive

In the Sphinx documentation, you can find a wonderfull documentation How to use only directive [1].

You can use a few mechanism to set tags, see How to use tags [2].

In the pipeline, we currently set the tag via command line option --tag tag_Linux. See .gitlab-ci.yml.

Example 1: only directive

.. only:: tag_Windows

   We are building currently for Windows via tag.

   .. need:: Need Tag Windows
      :id: N_VARIANT_TAG_WINDOWS

.. only:: tag_MacOS

   We are building currently for MacOS via tag.

   .. need:: Need Tag MacOS
      :id: N_VARIANT_TAG_MACOS

.. only:: tag_Linux

   We are building currently for Linux via tag.

   .. need:: Need Tag Linux
      :id: N_VARIANT_TAG_LINUX

We are building currently for Linux via tag.

Warning

Sphinx is always processing the content inside of the only directive, but is discarding the output if not needed. So if you create objects within the only directive, they are available to the datamodel. See the following needtable for demonstration.

ID

Title

Status

Type

Outgoing

Tags

N_VARIANT_TAG_LINUX

Need Tag Linux

need

N_VARIANT_TAG_MACOS

Need Tag MacOS

need

N_VARIANT_TAG_WINDOWS

Need Tag Windows

need

Sphinx: ifconfig Directive

In the Sphinx documentation, you can find a wonderfull documentation How to use ifconfig [3].

This can be used with How to overwrite configuration parameter [4].

  1. Add the ifconfig directive to the extensions in conf.py.

    Listing 9 How-to add ifconfig directive to the extensions
    extensions = [
       #...
       'sphinx.ext.ifconfig',
       #...
    ]
    
  2. Add your configuration parameter to conf.py

    Listing 10 How-to add a customer configuration value to sphinx
    1def setup(app):
    2    app.add_config_value(
    3        name = 'my_ifconfig',
    4        default = '',
    5        rebuild = 'html',
    6        types = frozenset({str})
    7        )
    
  3. Overwrite the configuration parameter in your sphinx-build sphinx-build [options] --define my_ifconfig='ifconfig_MacOS' <sourcedir> <outputdir>

  4. Use the ifconfig directive in your rst files

    Example 2: ifconfig directive

    .. ifconfig:: my_ifconfig == "ifconfig_Windows"
    
       We are building currently for Windows via ifconfig.
    
       .. need:: Need ifconfig Windows
          :id: N_VARIANT_IFCONFIG_WINDOWS
    
    .. ifconfig:: my_ifconfig == "ifconfig_MacOS"
    
       We are building currently for MacOS via ifconfig.
    
       .. need:: Need ifconfig MacOS
          :id: N_VARIANT_IFCONFIG_MACOS
    
    .. ifconfig:: my_ifconfig == "ifconfig_Linux"
    
       We are building currently for Linux via ifconfig.
    
       .. need:: Need ifconfig Linux
          :id: N_VARIANT_IFCONFIG_LINUX
    

    We are building currently for MacOS via ifconfig.

Warning

Sphinx is always processing the content inside of the ifconfig directive, but is discarding the output if not needed. So if you create objects within the ifconfig directive, they are available to the datamodel. See the following needtable for demonstration.

ID

Title

Status

Type

Outgoing

Tags

N_VARIANT_IFCONFIG_LINUX

Need ifconfig Linux

need

N_VARIANT_IFCONFIG_MACOS

Need ifconfig MacOS

need

N_VARIANT_IFCONFIG_WINDOWS

Need ifconfig Windows

need

Collections: if-collection Directive

In the Useblocks Collections extension documentation, you can find a wonderfull documentation How to use if-collection [5].

  1. For sure you have to add the sphinxcontrib.collections extension from useblocks to your extensions in conf.py.

    Listing 11 How-to add sphinxcontrib.collections extension to the extensions
    extensions = [
       #...
       'sphinxcontrib.collections',
       #...
    ]
    
  2. Configure collections in conf.py.

    Listing 12 How-to configure collections extension
    140
    141from sphinxcontrib.collections.drivers import Driver
    142from sphinxcontrib.collections.api import register_driver
    143
    144class VariantDriver(Driver):
    145    def run(self):
    146        if 'tags' in self.config:
    147            self.info('Run VariantDriver with tags: {}'.format(self.config['tags']))
    148        else:
    149            self.info('Run VariantDriver without tags')
    150
    151    def clean(self):
    152        pass
    153
    154register_driver('VariantDriver', VariantDriver)
    155
    156collections = {
    157    'collection_Windows': {
    158        'driver': 'VariantDriver',
    159        'active': False,
    160        'tags': ['tag_Windows'],
    161    },
    162    'collection_MacOS': {
    163        'driver': 'VariantDriver',
    164        'active': False,
    165        'tags': ['tag_MacOS'],
    166    },
    167    'collection_Linux': {
    168        'driver': 'VariantDriver',
    169        'active': False,
    170        'tags': ['tag_Linux'],
    171    },
    172}
    
  3. Use it in your rst files:

    Example 3: useblocks Collections: if-collection Directive

    .. if-collection:: collection_Windows
    
       We are building currently for Windows via if-collection.
    
       .. need:: Need if-collection Windows
          :id: N_VARIANT_COLLECTION_WINDOWS
    
    .. if-collection:: collection_MacOS
    
       We are building currently for MacOS via if-collection.
    
       .. need:: Need if-collection MacOS
          :id: N_VARIANT_COLLECTION_MACOS
    
    .. if-collection:: collection_Linux
    
       We are building currently for Linux via if-collection.
    
       .. need:: Need if-collection Linux
          :id: N_VARIANT_COLLECTION_LINUX
    

    We are building currently for Linux via if-collection.

ID

Title

Status

Type

Outgoing

Tags

N_VARIANT_COLLECTION_LINUX

Need if-collection Linux

need

Sphinx-Ifelse

Check the pypi package sphinx-ifelse.

  1. For sure you have to add the sphinx-ifelse extension to your extensions in conf.py.

    Listing 13 How-to add sphinx-ifelse extension to the extensions
    extensions = [
       #...
       'sphinx_ifelse',
       #...
    ]
    
  2. Configure ifelse_variants in conf.py.

    Listing 14 How-to configure ifelse_variants
    132
    133ifelse_variants = {
    134   'ifelse_OS': 'ifelse_Linux',
    135}
    136
    
  3. Use it in your rst files:

    Example 4: Sphinx-Ifelse:

    .. if:: ifelse_OS == "ifelse_Windows"
    
       We are building currently for Windows via ifelse.
    
       .. need:: Need ifelse Windows
          :id: N_VARIANT_IFELSE_WINDOWS
    
    .. elif:: ifelse_OS == "ifelse_MacOS"
    
       We are building currently for MacOS via ifelse.
    
       .. need:: Need ifelse MacOS
          :id: N_VARIANT_IFELSE_MACOS
    
    .. elif:: ifelse_OS == "ifelse_Linux"
    
       We are building currently for Linux via ifelse.
    
       .. need:: Need ifelse Linux
          :id: N_VARIANT_IFELSE_LINUX
    
    .. else::
    
       We are building currently for an unknown OS via ifelse.
    
       .. need:: Need ifelse OS Unknown
          :id: N_VARIANT_IFELSE_OS_UNKNOWN
    

    We are building currently for Linux via ifelse.

    Warning

    You can write headlines / sections in the content of the ifelse directive. But you have to be careful with the correct ordering of sections in all possible output variants.

ID

Title

Status

Type

Outgoing

Tags

N_VARIANT_IFELSE_LINUX

Need ifelse Linux

need

Sphinx-Needs: Attribute Variants

In the Sphinx-Needs documentation, you can find a wonderfull documentation How to define Sphinx-Needs variants [6].

  1. For sure you have to add the sphinx-needs extension to your extensions in conf.py.

    Listing 15 How-to add sphinx-needs extension to the extensions
    extensions = [
       #...
       'sphinx_needs',
       #...
    ]
    
  2. Configure needs_variants and needs_variant_options in conf.py.

    Listing 16 How-to configure needs_variants and needs_variant_options
     1
     2needs_variants = {
     3    "var_Windows": "True" if 'tag_Windows' in tags else "False",
     4    "var_MacOS": "True", # Manuel set to True
     5    "var_Linux": "True" if 'tag_Linux' in tags else "False",
     6    # You can change logic to set the variant
     7}
     8
     9needs_variant_options = [
    10    "status",
    11    "test_status",
    12    "satisfies",
    13]
    14
    
  3. Use it in your rst files:

    Example 5: Sphinx-Needs: Attribute Variants

    .. need:: A need with variants
       :id: N_EXAMPLE_VARIANTS
       :status: var_MacOS:MacOS, var_Linux:Linux,not set
       :test_status: var_MacOS:set with variant,not set
       :satisfies: var_MacOS:N_EXAMPLE_VARIANTS_ORDERING
    
    .. need:: A need with variants (with different ordering)
       :id: N_EXAMPLE_VARIANTS_ORDERING
       :status: var_Linux:Linux, var_MacOS:MacOS,not set
       :test_status: [tag_Linux]:set with sphinx-tag,not set
    

    Warning

    If your are using sphinx tags, these are not always set, you will get a warning:

    .. need:: A need with variants which creates a warning
       :id: N_EXAMPLE_VARIANTS_WARNING
       :status: var_MacOS: MacOS, var_Linux: Linux, not set
       :test_status: [tag_MacOS]: set with sphinx-tag, not set
    

    In the example, we will get WARNING: Error in filter 'tag_MacOS': name 'tag_MacOS' is not defined [needs.variant].

ID

Title

Status

Type

Outgoing

Tags

N_EXAMPLE_VARIANTS

A need with variants

MacOS

need

N_EXAMPLE_VARIANTS_ORDERING

A need with variants (with different ordering)

Linux

need

Jinja2 templates

This inspired by How to integrate jinja2 in rst [7].

  1. Define jinja_context and jinja2rst with variant information in conf.py.

    Listing 17 How-to configure jinja_context and jinja2rst
     1jinja_context = {
     2    'jinja_OS': 'QNX',
     3    'realtime': True,
     4}
     5
     6
     7def jinja2rst(app, docname, source):
     8    """
     9    Render our pages as a jinja template for fancy templating goodness.
    10    """
    11    # Make sure we're outputting HTML as builder.templates is not availalbe in other builder
    12    if app.builder.format != 'html':
    13        return
    14
    15    # In this demo, we only want to process content of 'variant_management' with jinja2
    16    if docname != 'variant_management':
    17        # Do nothing additionally
    18        return
    19
    20    src = source[0]
    21    rendered = app.builder.templates.render_string(
    22        src, jinja_context
    23    )
    24    source[0] = rendered
    25
    
  2. Connect jinja2rst in Sphinx to source-read event in conf.py.

    Listing 18 How-to connect jinja2rst to source-read event
    1def setup(app):
    2    app.connect("source-read", jinja2rst)
    

    Warning

    If you run jinja2 on all files, you do have to think about other instances of jinja2 in your rst files. E.g. if you use jinja2 in your rst files, you have to use the raw directive to prevent jinja2 from processing the content. In Sphinx-Needs are few directives which are using jinja2, e.g. needuml or needs-templates.

  3. Use it in your rst files:

    Example 6: Example: Jinja2 templates

    We are building currently for QNX via jinja2 template.
    
    .. need:: Need Jinja2 QNX
       :id: N_VARIANT_JINJA2_QNX
       :status: set by template
       :satisfies: N_ALWAYS_JINJA2_REALTIME
    
    
    .. need:: Need Jinja2 realtime
       :id: N_ALWAYS_JINJA2_REALTIME
    

    We are building currently for QNX via jinja2 template.

    After we cannot use the example directive here; following you find a manual copy of the the authored rst file. We do have to encapsulate the jinja2 template in a raw tag, otherwise the jinja2 template will be processed and reduced by jinja2 again.

    Example: Manual copy of Jinja2 template

     1{%if jinja_OS%}
     2We are building currently for {{jinja_OS}} via jinja2 template.
     3
     4.. need:: Need Jinja2 {{jinja_OS}}
     5   :id: N_VARIANT_JINJA2_{{jinja_OS}}
     6   :status: {%if jinja_OS == 'QNX'%}set by template{%else%}not set{%endif%}
     7   {%if realtime%}:satisfies: N_ALWAYS_JINJA2_REALTIME{%endif%}
     8{%else%}
     9We are building currently for an unknown OS via jinja2 template.
    10
    11.. need:: Need Jinja2 OS Unknown
    12   :id: N_VARIANT_JINJA2_OS_UNKNOWN
    13{%endif%}
    14
    15.. need:: Need Jinja2 realtime
    16   :id: N_ALWAYS_JINJA2_REALTIME
    

ID

Title

Status

Type

Outgoing

Tags

N_ALWAYS_JINJA2_REALTIME

Need Jinja2 realtime

need

N_VARIANT_JINJA2_QNX

Need Jinja2 QNX

set by template

need

Comparision of the different variant mechanisms

The table below is a summary of the different mechanisms. It is not complete and does not cover all use cases. Please check the documentation of the different mechanisms for more details.

The rating for the comparision table:

Table 37 Rating

Symbol

Description

- -

not supported

-

possible, but not recommended

+

possible, but drawbacks

+ +

possible and recommended

Table 38 Comparision Table
Name
Advantages
Disadvantages
Manage complete
Need Variants
Manage Attributes
/ Links of Needs

only

  • Can change complete parts of the documentation

  • Build-in sphinx directive

  • Always add the elements to the datamodel

  • Does not support else or elif

+

- -

ifconfig

  • Can change complete parts of the documentation

  • Build-in sphinx directive

  • Always add the elements to the datamodel

  • Does not support else or elif

+

- -

if-collection

  • Can change complete parts of the documentation

  • Wonderfull to be combined with content from collections

  • Has to be installed and configured

  • Does not support else or elif

+

- -

ifelse

  • Can change complete parts of the documentation easly

  • Support else or elif

  • Has to be installed and configured

+ +

- -

Sphinx-Needs
Attribute Variants
  • Build-in sphinx-needs directive

  • Support else or elif

  • Can change attributes and links of needs elements

  • Can only change attributes of needs

  • Attention with combintion of tags

- -

+ +

jinja2
templates
  • Can change everything depending on the context

  • Is difficult to debug

  • Nesting of jinja2 templates is difficult,
    but often requested e.g.
    with use of needuml or needs-templates

+

+

References