Why not to add `dict()` to CMake?

Hi,

It really pains to store and manipulate key-value pairs nowadays in CMake. Why not add dict() command for that?

dict(INSERT <dict_var> <key> <value>)
dict(REMOVE <dict_var> <key>)
dict(GET  <dict_var> <key> <output_variable>)

foreach(key value IN DICTS <dict_var>...)
...

Even that minimal commands could help A LOT

I think the basic problem is the stringly-typed language. I think the main issue would be coming up with an encoding scheme for these variables in the current “type system”. How would (should?) the cache editors handle them? Cache file format for storage of them? What happens when you do message("${dict_var}")? How about some_call(${dict_var})? Passing them to scripts via -D?

Unfortunately, I think that this is something that depends on a ship that sailed long ago (late 90’s).

I think there was a proposal/issue for a json command that manipulates JSON objects (which do have an unambiguous string representation if we say “no extra whitespace”; a json(PRETTY) could exist though). Cache editing would still be an interesting question though.

Nowadays CMake supports “nested lists” (i.e. a list variable wrapped into [ / ] can be atomically inserted/extracted into/from another list).
Yeah, the support from the list() command for that syntax still need some care, but “manually” it’s quite possible.

What about wrapping dict pairs into { / }? (Yeah, lets reserve ( / ) for tuples ;).
A dict than can be stored similar to lists (w/ ; separator) and have items wrapped into { / }
Then message command just prints its raw representation (like for list variables nowadays).
Same for some_call() – it receives a raw string, but being used w/ dict() it’ll be possible to do smth w/ it (just like list vars today)…

That may work, not sure if it’d be acceptable though. I’ve had ideas for CMake to cache the various parsings of variables into variables (e.g., if we parse a variable as a list, cache the vector<string_view> for the list components). For this to be performant, that caching may need to exist.

Other questions that come to mind (about usage and encoding into CMake’s strings):

  • What is our behavior with multi-value dictionaries?
  • Multi-value keys in the dictionary?
  • What happens if you set a dictionary key to a dictionary? List? What about a dictionary value?

All of these are possible because the dictionary has a CMake string encoding and string(APPEND) and set() exist.

@slurps-mad-rips Thoughts? I know you’ve delved into data structures in CMake before.

CMake’s language does not have support for arbitrary content in lists because there is no escape mechanism for [ or ], and the escaping for ; only survives one layer. Variable values are always strings and are only interpreted as lists (or numbers, versions, etc.) in specific contexts. I don’t think trying to offer magic interpretation as a dictionary will work well.

Any other thoughts? %)
Lack of dicts in CMake gives a lot of pain and limitations :frowning:

And yes, recently I used list-of-lists in “manual” mode… that was hard… and tons of CMake code :frowning:

This article gives some nice ideas: https://dev.to/slurpsmadrips/everything-you-never-wanted-to-know-about-cmake-4mgg

1 Like

@McMartin ,

Many thanks! Reading it I feel exciting and disappointing at the same time %) – How dare the author can be if he wants smth… and I’m deeply disappointed for CMake

I don’t think trying to offer magic interpretation as a dictionary will work well.

The discussion I’ve started here is to find a reliable way to have dict() naturally in CMake, cuz obviously it is highly wanted feature. I admit it could be hard and complex… but IMO it worth it!

PS
@marc.chevrier, @brad.king

From the blog post above:

We either must rely on content being stored in a CMake safe format, regexes, or reading one byte at a time in the CMake language (No thank you! 🙅).

Yet another case for foreach(... IN STRINGS...) recently rejected

CMake is not a programming language, so I’m somewhat wary of efforts to build advanced high-level features into the language itself. At the risk of sounding dismissive, I’ll note that beyond assertions of “a lot of pain and limitations” caused by the lack of an arbitrary key-value datatype, nobody’s really provided even a single concrete example of why it’s needed. Desired, sure, but not unavoidably necessary.

(The blog post linked above — which also fails to demonstrate the actual need for a dict type — is even more dismissive:

Perhaps this might change in the future, and we’ll get a real honest to god dictionary type, but don’t hold your breath. I’d rather see the CMake language go away entirely than get a dictionary type. :slightly_smiling_face:

So I don’t feel so bad, I guess is what I’m saying.)

Besides, for non-arbitrary key-value pairings, CMake’s existing list and string variables already serve. This sort of pattern is used all the time in Find modules:

foreach(name record1 record2 record3 record4)
  foreach(k key1 key2 key3 key4)
    set(_${name}_${k} <something to determine ${k} value for ${name}>)
  endforeach()
endforeach()

Yes, the list of keys has to be predefined, you can’t easily have each record contain different keys, or arbitrary keys that are only determined at runtime. (Though any of the _${name}_${k} variables can certainly be empty, if that key isn’t needed for a particular ${name}.)

Can key-value pairs be passed between CMake contexts easily? No. Can they be passed with a little work? Most likely yes, using target PROPERTIES (which can, recall, be arbitrary). There’s nothing to stop you from later doing this:

foreach (name ...)
  add_custom_target(${name})
  foreach (k ...)
    set_target_properties(${name} PROPERTIES ${k} "${_${name}_${k}}")
  endforeach()
endforeach()

Then, as the set_target_properties() documentation notes, “You can use any prop value pair you want and extract it later with the get_property() or get_target_property() command.”
So, if you were to include the above code in a “dict.cmake” file, and then later in your CMakeLists.txt you were to call:

set(CMAKE_MODULE_PATH ".")
include(dict)

include(CMakePrintHelpers)
cmake_print_properties(TARGETS record1 record2 PROPERTIES key1 key2)

Well, I just tried it, so I can tell you. Using this as my modified “dict creation” loop:

foreach(name record1 record2 record3 record4)
  foreach(n 1 2 3 4)
    set("_${name}_key${n}" "${name}.value${n}")
  endforeach()
endforeach()

I get this:

$ cmake .                     
-- 
 Properties for TARGET record1:
   record1.key1 = "record1.value1"
   record1.key2 = "record1.value2"
 Properties for TARGET record2:
   record2.key1 = "record2.value1"
   record2.key2 = "record2.value2"

-- Configuring done
-- Generating done
-- Build files have been written to: /tmp

Effectively, each target is a dict of its properties.