xref: /llvm-project/libc/cmake/modules/LibcConfig.cmake (revision 14e20eebd13c28770a92120696dc60754de4c139)
1# This cmake module contains utilities to read and load libc config options
2# listed in config.json files.
3#
4# The JSON parsing commands that CMake provides are rather tedious to use.
5# Below is a quick reference which tries to map the CMake JSON parsing
6# commands to the Python dictionary API.
7#
8# * There is no way to iterate over the JSON items. One will first
9#   have to find the number of items using string(JSON ... LENGTH ...)
10#   command, and then iterate over the items using foreach(... RANGE ...).
11# * The way to get the key from the JSON dictionary is to use the index
12#   of the item and the string(JSON ... MEMBER ... $<index>) function.
13# * Once you have the key, you can use the string(JSON ... GET ... $<key>)
14#   function to get the value corresponding to the key.
15
16# Fill |opt_list| with all options listed in |config_file|. For each option,
17# the item added to |opt_list| is the dictionary of the form:
18#   {
19#     "<option name>": {
20#       "value: <option value>,
21#       "doc": "<option doc string>",
22#     }
23#   }
24# Each of the above items can be parsed again with the string(JSON ...)
25# command.
26# This function does nothing if |config_file| is missing.
27function(read_libc_config config_file opt_list)
28  if(NOT EXISTS ${config_file})
29    return()
30  endif()
31  # We will assume that a config file is loaded only once and that
32  # each config file loaded will affect config information. Since
33  # we want a change to config information to trigger reconfiguration,
34  # we add the |config_file| to the list of files the configure itself
35  # should depend on.
36  set_property(
37    DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
38    PROPERTY CMAKE_CONFIGURE_DEPENDS ${config_file})
39
40  file(READ ${config_file} json_config)
41  string(JSON group_count ERROR_VARIABLE json_error LENGTH ${json_config})
42  if(json_error)
43    message(FATAL_ERROR "${config_file}: ${json_error}")
44  endif()
45  if(${group_count} EQUAL 0)
46    # This "if" conditions becomes active if there are no config options
47    # to load. If there are no config options, it is better to remove that
48    # config.json file instead of including an empty file.
49    message(FATAL_ERROR "${config_file}: Does not contain any config option groups")
50  endif()
51  math(EXPR group_count_1 "${group_count} - 1")
52
53  set(optname_list)
54  foreach(group_num RANGE ${group_count_1})
55    # The group names are the keys of the global dictionary. So, we first
56    # lookup the group name or the key for each item in the dictionary.
57    string(JSON group_name ERROR_VARIABLE json_error MEMBER ${json_config} ${group_num})
58    if(json_error)
59      message(FATAL_ERROR "${config_file}: ${json_error}")
60    endif()
61
62    # Once we have the group name, we GET the option map for that group, which
63    # is the value corresponding to the group name key.
64    string(JSON option_map ERROR_VARIABLE json_error GET ${json_config} ${group_name})
65    if(json_error)
66      message(FATAL_ERROR ${json_error})
67    endif()
68    string(JSON option_count ERROR_VARIABLE jsor_error LENGTH ${option_map})
69    if(json_error)
70      message(FATAL_ERROR ${json_error})
71    endif()
72    if(${option_count} EQUAL 0)
73      message(FATAL_ERROR "${config_file}: No options listed against the config option group '${group_name}'")
74    endif()
75
76    math(EXPR option_count_1 "${option_count} - 1")
77    foreach(opt_num RANGE ${option_count_1})
78      string(JSON option_name ERROR_VARIABLE json_error MEMBER ${option_map} ${opt_num})
79      if(json_error)
80        message(FATAL_ERROR ${json_error})
81      endif()
82      list(FIND optname_list ${option_name} optname_exists)
83      if(${optname_exists} GREATER -1)
84        message(FATAL_ERROR "${config_file}: Found duplicate option name: ${option_name}")
85      endif()
86      list(APPEND optname_list ${option_name})
87
88      string(JSON optdata ERROR_VARIABLE json_error GET ${option_map} ${option_name})
89      if(json_error)
90        message(FATAL_ERROR ${json_error})
91      endif()
92      set(opt "{\"${option_name}\": ${optdata}}")
93      list(APPEND all_opts ${opt})
94    endforeach()
95  endforeach()
96  set(${opt_list} ${all_opts} PARENT_SCOPE)
97endfunction()
98
99# Loads the config options listed in |config_file| in the following way:
100# * For each option listed in the |config_file|, it looks for existence of a
101#   var with the same name. It is an error if the var is not already defined.
102#   If a var with the option name is found, then its value is overwritten
103#   with the value specified in |config_file|.
104# * If there are options which are not to be overriden, then the list of
105#   such options can be passed to this function after the |config_file|
106#   argument. Typically, these will be the options specified on the CMake
107#   command line.
108function(load_libc_config config_file)
109  read_libc_config(${config_file} file_opts)
110  foreach(opt IN LISTS file_opts)
111    string(JSON opt_name ERROR_VARIABLE json_error MEMBER ${opt} 0)
112    if(json_error)
113      message(FATAL_ERROR ${json_error})
114    endif()
115    if(NOT DEFINED ${opt_name})
116      message(FATAL_ERROR " Option ${opt_name} defined in ${config_file} is invalid.")
117    endif()
118    if(ARGN)
119      list(FIND ARGN ${opt_name} optname_exists)
120      if(${optname_exists} GREATER -1)
121        # This option is not to be overridden so just skip further processing.
122        continue()
123      endif()
124    endif()
125    string(JSON opt_object ERROR_VARIABLE json_error GET ${opt} ${opt_name})
126    if(json_error)
127      message(FATAL_ERROR ${json_error})
128    endif()
129    string(JSON opt_value ERROR_VARIABLE jsor_error GET ${opt_object} "value")
130    if(json_error)
131      message(FATAL_ERROR ${json_error})
132    endif()
133    message(STATUS "Overriding - ${opt_name}: ${opt_value} (Previous value: ${${opt_name}})")
134    set(${opt_name} ${opt_value} PARENT_SCOPE)
135  endforeach()
136endfunction()
137
138function(generate_config_doc config_file doc_file)
139  if(NOT EXISTS ${config_file})
140    message(FATAL_ERROR "${config_file} does not exist")
141  endif()
142  file(READ ${config_file} json_config)
143  string(JSON group_count ERROR_VARIABLE json_error LENGTH ${json_config})
144  if(json_error)
145    message(FATAL_ERROR "${config_file}: ${json_error}")
146  endif()
147  if(${group_count} EQUAL 0)
148    message(FATAL_ERROR "${config_file}: Does not contain any config option groups")
149  endif()
150  math(EXPR group_count_1 "${group_count} - 1")
151
152  set(doc_string ".. _configure:\n"
153                 "..\n"
154                 "   Do not edit this file directly. CMake will auto generate it.\n"
155                 "   If the changes are intended, add this file to your commit.\n"
156                 "\n"
157                 "==========================\n"
158                 "Configure Options\n"
159                 "==========================\n"
160                 "\n"
161                 "Below is the full set of options one can use to configure the libc build.\n"
162                 "An option can be given an explicit value on the CMake command line using\n"
163                 "the following syntax:\n"
164                 "\n"
165                 ".. code-block:: sh\n"
166                 "\n"
167                 "  $> cmake <other build options> -D<libc config option name>=<option value> <more options>\n"
168                 "\n"
169                 "For example:\n"
170                 "\n"
171                 ".. code-block:: sh\n"
172                 "\n"
173                 "  $> cmake <other build options> -DLIBC_CONF_PRINTF_DISABLE_FLOAT=ON <more options>\n"
174                 "\n"
175                 "See the main ``config/config.json``, and the platform and architecture specific\n"
176                 "overrides in ``config/<platform>/config.json`` and ``config/<platform>/<arch>/config.json,``\n"
177                 "to learn about the defaults for your platform and target.\n"
178                 "\n")
179
180  foreach(group_num RANGE ${group_count_1})
181    string(JSON group_name ERROR_VARIABLE json_error MEMBER ${json_config} ${group_num})
182    if(json_error)
183      message(FATAL_ERROR "${config_file}: ${json_error}")
184    endif()
185    string(APPEND doc_string "* **\"${group_name}\" options**\n")
186    string(JSON option_map ERROR_VARIABLE json_error GET ${json_config} ${group_name})
187    if(json_error)
188      message(FATAL_ERROR ${json_error})
189    endif()
190    string(JSON option_count ERROR_VARIABLE jsor_error LENGTH ${option_map})
191    if(json_error)
192      message(FATAL_ERROR ${json_error})
193    endif()
194    if(${option_count} EQUAL 0)
195      message(FATAL_ERROR "${config_file}: No options listed against the config option group '${group_name}'")
196    endif()
197
198    math(EXPR option_count_1 "${option_count} - 1")
199    foreach(opt_num RANGE ${option_count_1})
200      string(JSON option_name ERROR_VARIABLE json_error MEMBER ${option_map} ${opt_num})
201      if(json_error)
202        message(FATAL_ERROR ${json_error})
203      endif()
204      string(JSON opt_object ERROR_VARIABLE json_error GET ${option_map} ${option_name})
205      if(json_error)
206        message(FATAL_ERROR "Error generating ${doc_file}: ${json_error}\n${opt_object}")
207      endif()
208      string(JSON opt_doc ERROR_VARIABLE json_error GET ${opt_object} "doc")
209      if(json_error)
210        message(FATAL_ERROR "Error generating ${doc_file}: ${json_error}")
211      endif()
212      string(APPEND doc_string "    - ``${option_name}``: ${opt_doc}\n")
213    endforeach()
214  endforeach()
215  message(STATUS "Writing config doc to ${doc_file}")
216  file(WRITE ${doc_file} ${doc_string})
217endfunction()
218