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

Commit f6dbbb6

Browse files
author
William McLendon
committed
TRILFRAME-128: Fixes to opt-set-cmake-options
1 parent 90005cd commit f6dbbb6

File tree

4 files changed

+137
-43
lines changed

4 files changed

+137
-43
lines changed

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)