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 |
---|---|---|---|---|---|
Need Tag Linux |
need |
||||
Need Tag MacOS |
need |
||||
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].
Add the ifconfig directive to the extensions in
conf.py
.extensions = [ #... 'sphinx.ext.ifconfig', #... ]
Add your configuration parameter to
conf.py
1def setup(app): 2 app.add_config_value( 3 name = 'my_ifconfig', 4 default = '', 5 rebuild = 'html', 6 types = frozenset({str}) 7 )
Overwrite the configuration parameter in your sphinx-build
sphinx-build [options] --define my_ifconfig='ifconfig_MacOS' <sourcedir> <outputdir>
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 |
---|---|---|---|---|---|
Need ifconfig Linux |
need |
||||
Need ifconfig MacOS |
need |
||||
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].
For sure you have to add the sphinxcontrib.collections extension from useblocks to your extensions in
conf.py
.extensions = [ #... 'sphinxcontrib.collections', #... ]
Configure
collections
inconf.py
.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}
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 |
---|---|---|---|---|---|
Need if-collection Linux |
need |
Sphinx-Ifelse
Check the pypi package sphinx-ifelse.
For sure you have to add the sphinx-ifelse extension to your extensions in
conf.py
.extensions = [ #... 'sphinx_ifelse', #... ]
Configure
ifelse_variants
inconf.py
.132 133ifelse_variants = { 134 'ifelse_OS': 'ifelse_Linux', 135} 136
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 |
---|---|---|---|---|---|
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].
For sure you have to add the sphinx-needs extension to your extensions in
conf.py
.extensions = [ #... 'sphinx_needs', #... ]
Configure
needs_variants
andneeds_variant_options
inconf.py
.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
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 |
---|---|---|---|---|---|
A need with variants |
MacOS |
need |
|||
A need with variants (with different ordering) |
Linux |
need |
Jinja2 templates
This inspired by How to integrate jinja2 in rst [7].
Define
jinja_context
andjinja2rst
with variant information inconf.py
.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
Connect
jinja2rst
in Sphinx to source-read event inconf.py
.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.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 |
---|---|---|---|---|---|
Need Jinja2 realtime |
need |
||||
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:
Symbol |
Description |
---|---|
- - |
not supported |
- |
possible, but not recommended |
+ |
possible, but drawbacks |
+ + |
possible and recommended |
Name
|
Advantages
|
Disadvantages
|
Manage complete
Need Variants
|
Manage Attributes
/ Links of Needs
|
---|---|---|---|---|
only |
|
|
+ |
- - |
ifconfig |
|
|
+ |
- - |
if-collection |
|
|
+ |
- - |
ifelse |
|
|
+ + |
- - |
Sphinx-Needs
Attribute Variants
|
|
|
- - |
+ + |
jinja2
templates
|
|
|
+ |
+ |