Skip to content
This repository was archived by the owner on Sep 3, 2024. It is now read-only.

Commit 2b54841

Browse files
author
William McLendon
committed
Merge branch 'TRILFRAME-128' into 'master'
Trilframe 128: Fix PARENT_SCOPE + FORCE error plus FORCE implies CACHE See merge request trilinos-devops-consolidation/code/SetProgramOptions!16
2 parents 90005cd + e38062a commit 2b54841

File tree

5 files changed

+150
-43
lines changed

5 files changed

+150
-43
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
## [0.3.1] <!-- YYYY-MM-DD --> [Unreleased]
2121
#### Added
2222
#### Changed
23+
- Changes to `opt-set-cmake-var` behaviour:
24+
- Trigger a WARNING if `PARENT_SCOPE` and a _TYPE_ is provided since CMake
25+
requires TYPED vars to be CACHE vars and `PARENT_SCOPE` will force the
26+
var to be considered a non CACHE var that would store the parameters
27+
before `PARENT_SCOPE` in the call as _list_ entries.
28+
- Trigger an exception if `PARENT_SCOPE` and `FORCE` are both provided since
29+
`FORCE` is only valid for CACHE variables but `PARENT_SCOPE` is only valid
30+
for non-CACHE vars (and as noted above will _force_ the `set()` operation
31+
to be a non-CACHE var which then triggers a CMake error because `FORCE` is
32+
invalid to that form of `set()`.
33+
- **bash** generated options that have a TYPE and `PARENT_SCOPE` are processed
34+
to generate the same _list_ string that would be generated by the matching
35+
`set()` operation (with the warning).
2336
- Revert(ish) the modifications from MR12 which removed the error when generating
2437
BASH output if there is an unhandled CMake variable left hanging around.
2538
Rather than make this an unavoidable error though, ExpandVarsInText now inherits

doc/source/SetProgramOptionsCMake.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,37 @@ Supported .ini File Operations
1515
:header-rows: 1
1616
:widths: 20,30,80
1717

18+
``opt-set-cmake-var``
19+
+++++++++++++++++++++
20+
**Usage**: ``opt-set-cmake-var <varname> [TYPE] [PARENT_SCOPE] [FORCE] : <value>``
21+
22+
A CMake variable set operation. This can be used to genrate bash command line options
23+
of the form ``-D<varname>[:<type>]=<value>`` or in a CMake fragment we may generate
24+
a ``set()`` command.
25+
26+
For information related to CMake Fragment generation of ``set()`` commands, see
27+
the `CMake docs <https://cmake.org/cmake/help/latest/command/set.html#command:set>`_ .
28+
29+
``PARENT_SCOPE`` and ``FORCE`` are mutually exclusive options and will result in an
30+
error being thrown.
31+
32+
An additional thing to note is that ``PARENT_SCOPE`` should not be used with ``CACHE``
33+
variables (i.e., *typed* variables). CMake will happily process this but you will likely
34+
get a result that you do not want. In a CMake fragment file:
35+
36+
.. code-block:: cmake
37+
:linenos:
38+
39+
set(FOO FOO_VALUE CACHE STRING "my docstring" PARENT_SCOPE)
40+
41+
Will result in the value of ``FOO`` being set to ``FOO_VALUE;CACHE;STRING;my docstring``
42+
which is unlikely to match expectations, but that is what CMake will do. In this case,
43+
``SetProgramOptionsCMake`` will raise a warning and will generate a string to match what
44+
CMake generates when sent to a bash options generator
45+
(i.e., ``-DFOO="FOO_VALUE;CACHE;STRING;my docstring"``).
46+
When sent through the *cmake fragment* generator the output will be the equivalent ``set()``
47+
call.
48+
1849

1950
Useful Links
2051
------------

src/setprogramoptions/SetProgramOptionsCMake.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,14 @@ def _program_option_handler_opt_set_cmake_var_bash(self, params, value) -> str:
202202
param_opts = self._helper_opt_set_cmake_var_parse_parameters(params)
203203

204204
params = ["-D", varname]
205-
if param_opts['TYPE'] is not None:
205+
206+
if param_opts['VARIANT'] == 1:
207+
# if PARENT_SCOPE was given to something that is typed and forced us to
208+
# be a type-1 variant, then we assign the list "<value>;CACHE;<type>;<docstring>"
209+
if param_opts['TYPE'] != None:
210+
value += f";CACHE;{param_opts['TYPE']};"
211+
212+
if param_opts['VARIANT'] == 2 and param_opts['TYPE'] is not None:
206213
params.append(":" + param_opts['TYPE'])
207214

208215
# Cache 'known' CMake vars here.
@@ -314,7 +321,7 @@ def _helper_opt_set_cmake_var_parse_parameters(self, params: list):
314321
because the relevance of some options depends on the back-end
315322
generator. For example, ``PARENT_SCOPE`` is relevant to a ``cmake_fragment``
316323
generator but does not get used for a ``bash`` option that would be
317-
provided to the ``camke`` application on the command line.
324+
provided to the ``cmake`` application on the command line.
318325
319326
Called By:
320327
@@ -330,14 +337,62 @@ def _helper_opt_set_cmake_var_parse_parameters(self, params: list):
330337
omission of flags that are applicable to a CMake Cache
331338
variable operation.
332339
"""
340+
default_cache_var_type = "STRING"
341+
333342
output = {'FORCE': False, 'PARENT_SCOPE': False, 'TYPE': None}
334343

335344
for option in params[: 4]:
336345
if option == "FORCE":
337346
output['FORCE'] = True
347+
# If FORCE is found but we have no TYPE yet, set to the default.
348+
if output['TYPE'] is None:
349+
output['TYPE'] = default_cache_var_type
338350
elif option == "PARENT_SCOPE":
339351
output['PARENT_SCOPE'] = True
340352
elif option in ["BOOL", "FILEPATH", "PATH", "STRING", "INTERVAL"]:
341353
output['TYPE'] = option
342354

355+
# Sanity Check(s)
356+
357+
# Case 1: PARENT_SCOPE and FORCE will cause a CMake Error
358+
if output['FORCE'] and output['PARENT_SCOPE']:
359+
msg = "ERROR: CMake does not allow `FORCE` and `PARENT_SCOPE` to both be used."
360+
self.exception_control_event("CATASTROPHIC", ValueError, message=msg)
361+
# Case 2: PARENT_SCOPE and CACHE will cause a CMake warning
362+
# and the value will include the cache entries as a list:
363+
# `set(FOO "VAL" CACHE STRING "docstring" PARENT_SCOPE)`
364+
# will result in `FOO` being set to "VAL;CACHE;STRING;docstring"
365+
# in the parent module's scope. This is probably not what someone
366+
# intended. Let this be a WARNING event though.
367+
# TBH: this should probably be a CATASTROPHIC but for now I'll
368+
# at least warn about it which is more than CMake does.
369+
if output['PARENT_SCOPE'] and output["TYPE"] != None:
370+
msg = "WARNING: Setting `PARENT_SCOPE` with `CACHE` parameters will result\n"
371+
msg += " in a non-CACHE variable being set containing a list of the\n"
372+
msg += " CACHE options. i.e., '<value>;CACHE;<type>;<docstring>'\n"
373+
msg += " which is probably not what is intended, but CMake will\n"
374+
msg += " not error or warn on this."
375+
self.exception_control_event("WARNING", ValueError, message=msg)
376+
377+
# Determine the variant of the ``set`` operation.
378+
# Type 1: ``set(<variable> <value>... [PARENT_SCOPE])``
379+
# Type 2: ``set(<variable> <value>... CACHE <type> <docstring> [FORCE])``
380+
381+
# PARENT_SCOPE forces Type-1 (i.e., non-cache var)
382+
# - This will override CACHE, at least as of CMake 3.21.x
383+
if output['PARENT_SCOPE']:
384+
output['VARIANT'] = 1
385+
386+
# FORCE implies CACHE. If type wasn't provided then it's a STRING
387+
elif output['FORCE']:
388+
output['VARIANT'] = 2
389+
390+
# If a TYPE is provided then it's a type-2 (CACHE) assignment.
391+
elif output['TYPE'] is not None:
392+
output['VARIANT'] = 2
393+
394+
# Otherwise, a simple set is a type-1
395+
else:
396+
output['VARIANT'] = 1
397+
343398
return output

src/setprogramoptions/unittests/files_ini/config_test_setprogramoptions.ini

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,29 +46,26 @@ use CMAKE_SOURCE_DIR
4646
opt-remove Trilinos_ENABLE_MueLu
4747
opt-remove MueLu SUBSTR
4848

49+
50+
[TEST_CMAKE_FAIL_ON_PARENT_SCOPE_AND_FORCE]
51+
# PARENT_SCOPE makes the value a non-cache option
52+
# but FORCE requires a cache type
53+
# CMake will error if you give both so we should raise an exception here.
54+
opt-set-cmake-var CMAKE_VAR_A PARENT_SCOPE FORCE : CMAKE_VAR_A_VAL
55+
56+
4957
[TEST_CMAKE_CACHE_PARAM_ORDER]
5058
opt-set-cmake-var CMAKE_VAR_A FORCE : ON
5159
opt-set-cmake-var CMAKE_VAR_B PARENT_SCOPE : ON
5260
opt-set-cmake-var CMAKE_VAR_C BOOL : ON
53-
54-
opt-set-cmake-var CMAKE_VAR_D FORCE PARENT_SCOPE : ON
55-
opt-set-cmake-var CMAKE_VAR_E PARENT_SCOPE FORCE : ON
56-
57-
opt-set-cmake-var CMAKE_VAR_F BOOL FORCE : ON
58-
opt-set-cmake-var CMAKE_VAR_G FORCE BOOL : ON
59-
61+
opt-set-cmake-var CMAKE_VAR_F BOOL FORCE : ON
62+
opt-set-cmake-var CMAKE_VAR_G FORCE BOOL : ON
6063
opt-set-cmake-var CMAKE_VAR_H BOOL PARENT_SCOPE : ON
6164
opt-set-cmake-var CMAKE_VAR_I PARENT_SCOPE BOOL : ON
6265

63-
opt-set-cmake-var CMAKE_VAR_J BOOL FORCE PARENT_SCOPE : ON
64-
opt-set-cmake-var CMAKE_VAR_K BOOL PARENT_SCOPE FORCE : ON
65-
opt-set-cmake-var CMAKE_VAR_L FORCE PARENT_SCOPE BOOL : ON
66-
opt-set-cmake-var CMAKE_VAR_M FORCE BOOL PARENT_SCOPE : ON
67-
opt-set-cmake-var CMAKE_VAR_N PARENT_SCOPE BOOL FORCE : ON
68-
opt-set-cmake-var CMAKE_VAR_O PARENT_SCOPE FORCE BOOL : ON
69-
7066
[TEST_CMAKE_CACHE_PARAM_TEST_02]
7167
# Validate what happens if there's a bad param.
68+
# Note: FORCE option will make this a CACHE var of type STRING
7269
opt-set-cmake-var CMAKE_VAR_A FORCE FOOBAR: ON
7370

7471

src/setprogramoptions/unittests/test_SetProgramOptionsCMake.py

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -367,42 +367,26 @@ def test_SetProgramOptionsCMake_param_order_01(self):
367367
self._execute_parser(parser, section)
368368

369369
option_list_bash_expect = [
370-
'-DCMAKE_VAR_A=ON',
370+
'-DCMAKE_VAR_A:STRING="ON"',
371371
'-DCMAKE_VAR_B=ON',
372372
'-DCMAKE_VAR_C:BOOL=ON',
373-
'-DCMAKE_VAR_D=ON',
374-
'-DCMAKE_VAR_E=ON',
375373
'-DCMAKE_VAR_F:BOOL=ON',
376374
'-DCMAKE_VAR_G:BOOL=ON',
377-
'-DCMAKE_VAR_H:BOOL=ON',
378-
'-DCMAKE_VAR_I:BOOL=ON',
379-
'-DCMAKE_VAR_J:BOOL=ON',
380-
'-DCMAKE_VAR_K:BOOL=ON',
381-
'-DCMAKE_VAR_L:BOOL=ON',
382-
'-DCMAKE_VAR_M:BOOL=ON',
383-
'-DCMAKE_VAR_N:BOOL=ON',
384-
'-DCMAKE_VAR_O:BOOL=ON'
375+
'-DCMAKE_VAR_H=ON;CACHE;BOOL;',
376+
'-DCMAKE_VAR_I=ON;CACHE;BOOL;'
385377
]
386378

387379
option_list_bash_actual = parser.gen_option_list(section, generator="bash")
388380
self.assertListEqual(option_list_bash_expect, option_list_bash_actual)
389381

390382
option_list_cmake_fragment_expect = [
391-
'set(CMAKE_VAR_A ON FORCE)',
383+
'set(CMAKE_VAR_A ON CACHE STRING "from .ini configuration" FORCE)',
392384
'set(CMAKE_VAR_B ON PARENT_SCOPE)',
393385
'set(CMAKE_VAR_C ON CACHE BOOL "from .ini configuration")',
394-
'set(CMAKE_VAR_D ON PARENT_SCOPE FORCE)',
395-
'set(CMAKE_VAR_E ON PARENT_SCOPE FORCE)',
396386
'set(CMAKE_VAR_F ON CACHE BOOL "from .ini configuration" FORCE)',
397387
'set(CMAKE_VAR_G ON CACHE BOOL "from .ini configuration" FORCE)',
398388
'set(CMAKE_VAR_H ON CACHE BOOL "from .ini configuration" PARENT_SCOPE)',
399389
'set(CMAKE_VAR_I ON CACHE BOOL "from .ini configuration" PARENT_SCOPE)',
400-
'set(CMAKE_VAR_J ON CACHE BOOL "from .ini configuration" PARENT_SCOPE FORCE)',
401-
'set(CMAKE_VAR_K ON CACHE BOOL "from .ini configuration" PARENT_SCOPE FORCE)',
402-
'set(CMAKE_VAR_L ON CACHE BOOL "from .ini configuration" PARENT_SCOPE FORCE)',
403-
'set(CMAKE_VAR_M ON CACHE BOOL "from .ini configuration" PARENT_SCOPE FORCE)',
404-
'set(CMAKE_VAR_N ON CACHE BOOL "from .ini configuration" PARENT_SCOPE FORCE)',
405-
'set(CMAKE_VAR_O ON CACHE BOOL "from .ini configuration" PARENT_SCOPE FORCE)'
406390
]
407391

408392
option_list_cmake_fragment_actual = parser.gen_option_list(section, generator="cmake_fragment")
@@ -414,6 +398,8 @@ def test_SetProgramOptionsCMake_param_order_01(self):
414398

415399
def test_SetProgramOptionsCMake_param_order_02(self):
416400
"""
401+
Tests that we correctly generate output if extra flags
402+
are provided such as something to uniqueify a .ini option entry.
417403
"""
418404
parser = self._create_standard_parser()
419405

@@ -424,7 +410,7 @@ def test_SetProgramOptionsCMake_param_order_02(self):
424410
# parse a section
425411
self._execute_parser(parser, section)
426412

427-
option_list_bash_expect = ['-DCMAKE_VAR_A=ON']
413+
option_list_bash_expect = ['-DCMAKE_VAR_A:STRING="ON"']
428414

429415
option_list_bash_actual = parser.gen_option_list(section, generator="bash")
430416
self.assertListEqual(option_list_bash_expect, option_list_bash_actual)
@@ -433,6 +419,34 @@ def test_SetProgramOptionsCMake_param_order_02(self):
433419
print("OK")
434420
return
435421

422+
def test_SetProgramOptionsCMake_fail_on_FORCE_and_PARENT_SCOPE(self):
423+
"""
424+
Tests the case that both PARENT_SCOPE and FORCE are provided.
425+
This will cause a CMake error beacuse the existence of PARENT_SCOPE
426+
forces CMake to use a Type-1 set operation, i.e. a NON-CACHEd
427+
variable. However ``FORCE`` is only valid for a CACHED variable (Type-2).
428+
These two options are mutually exclusive and CMake will fail.
429+
430+
In this case SetProgramOptionsCMake should raise a CATASTROPHIC
431+
error because the operation provided is invalid.
432+
"""
433+
parser = self._create_standard_parser()
434+
435+
print("-----[ TEST BEGIN ]----------------------------------------")
436+
section = "TEST_CMAKE_FAIL_ON_PARENT_SCOPE_AND_FORCE"
437+
print("Section : {}".format(section))
438+
439+
# parse a section
440+
self._execute_parser(parser, section)
441+
442+
with self.assertRaises(ValueError):
443+
parser.gen_option_list(section, generator="bash")
444+
445+
print("-----[ TEST END ]------------------------------------------")
446+
447+
print("OK")
448+
return
449+
436450
def test_SetProgramOptionsCMake_test_STRING_value_surrounded_by_double_quotes(self):
437451
"""
438452
Test STRING values are surrounded by double quotes.
@@ -445,10 +459,7 @@ def test_SetProgramOptionsCMake_test_STRING_value_surrounded_by_double_quotes(se
445459
section = "TEST_STRING_DOUBLE_QUOTES"
446460
print("Section : {}".format(section))
447461

448-
option_list_expect = [
449-
'-DFOO:STRING="foo::bar::baz<Type>"',
450-
'-DBAR:STRING="600"'
451-
]
462+
option_list_expect = ['-DFOO:STRING="foo::bar::baz<Type>"', '-DBAR:STRING="600"']
452463
option_list_actual = parser.gen_option_list(section, generator="bash")
453464

454465
print("-" * 40)
@@ -478,15 +489,15 @@ def test_SetProgramOptionsCMake_opt_remove(self):
478489
section = "TEST_CMAKE_VAR_REMOVE"
479490
print("Section : {}".format(section))
480491
option_list_bash_actual = parser.gen_option_list('TEST_CMAKE_VAR_REMOVE', 'bash')
481-
option_list_bash_expect = [ '-DBAR_TEST=BAR', '-DFOO=BAZ' ]
492+
option_list_bash_expect = ['-DBAR_TEST=BAR', '-DFOO=BAZ']
482493
self.assertListEqual(option_list_bash_expect, option_list_bash_actual)
483494
print("-----[ TEST END ]------------------------------------------")
484495

485496
print("-----[ TEST BEGIN ]----------------------------------------")
486497
section = "TEST_CMAKE_VAR_REMOVE"
487498
print("Section : {}".format(section))
488499
option_list_cmake_fragment_actual = parser.gen_option_list('TEST_CMAKE_VAR_REMOVE', 'cmake_fragment')
489-
option_list_cmake_fragment_expect = [ 'set(BAR_TEST BAR)', 'set(FOO BAZ)' ]
500+
option_list_cmake_fragment_expect = ['set(BAR_TEST BAR)', 'set(FOO BAZ)']
490501
self.assertListEqual(option_list_cmake_fragment_expect, option_list_cmake_fragment_actual)
491502
print("-----[ TEST END ]------------------------------------------")
492503

0 commit comments

Comments
 (0)