1include(ExternalProject)
2
3function(__kconfig_init)
4    if(${CMAKE_HOST_SYSTEM_NAME} MATCHES "FreeBSD")
5        set(MAKE_COMMMAND "gmake")
6    else()
7        set(MAKE_COMMMAND "make")
8    endif()
9
10    idf_build_get_property(idf_path IDF_PATH)
11    if(CMAKE_HOST_WIN32 AND DEFINED ENV{MSYSTEM})
12        # Prefer a prebuilt mconf-idf on Windows
13        find_program(WINPTY winpty)
14        unset(MCONF CACHE)  # needed when MSYS and CMD is intermixed (cache could contain an incompatible path)
15        find_program(MCONF mconf-idf)
16
17        # Fall back to the old binary which was called 'mconf' not 'mconf-idf'
18        if(NOT MCONF)
19            find_program(MCONF mconf)
20            if(MCONF)
21                message(WARNING "Falling back to mconf binary '${MCONF}' not mconf-idf. "
22                    "This is probably because an old version of IDF mconf is installed and this is fine. "
23                    "However if there are config problems please check the Getting Started guide for your platform.")
24            endif()
25        endif()
26
27        if(NOT MCONF)
28            find_program(NATIVE_GCC gcc)
29            if(NOT NATIVE_GCC)
30                message(FATAL_ERROR
31                    "Windows requires an MSYS2 version of gcc on the PATH to build mconf-idf. "
32                    "Consult the setup docs for ESP-IDF on Windows.")
33            else()
34                # Use the existing Makefile to build mconf (out of tree) when needed
35                #
36                set(MCONF ${CMAKE_BINARY_DIR}/kconfig_bin/mconf-idf)
37                set(src_path ${idf_path}/tools/kconfig)
38
39                # note: we preemptively remove any build files from the src dir
40                # as we're building out of tree, but don't want build system to
41                # #include any from there that were previously build with/for make
42                externalproject_add(mconf-idf
43                    SOURCE_DIR ${src_path}
44                    CONFIGURE_COMMAND ""
45                    BINARY_DIR "${CMAKE_BINARY_DIR}/kconfig_bin"
46                    BUILD_COMMAND rm -f ${src_path}/zconf.lex.c ${src_path}/zconf.hash.c
47                    COMMAND ${MAKE_COMMMAND} -f ${src_path}/Makefile mconf-idf
48                    BUILD_BYPRODUCTS ${MCONF}
49                    INSTALL_COMMAND ""
50                    EXCLUDE_FROM_ALL 1
51                    )
52
53                file(GLOB mconf_srcfiles ${src_path}/*.c)
54                list(REMOVE_ITEM mconf_srcfiles "${src_path}/zconf.lex.c" "${src_path}/zconf.hash.c")
55                externalproject_add_stepdependencies(mconf-idf build
56                    ${mconf_srcfiles}
57                    ${src_path}/Makefile
58                    ${CMAKE_CURRENT_LIST_FILE})
59                unset(mconf_srcfiles)
60                unset(src_path)
61
62                set(menuconfig_depends DEPENDS mconf-idf)
63            endif()
64        else()
65            execute_process(COMMAND "${MCONF}" -v
66                RESULT_VARIABLE mconf_res
67                OUTPUT_VARIABLE mconf_out
68                ERROR_VARIABLE mconf_err)
69            if(${mconf_res})
70                message(WARNING "Failed to detect version of mconf-idf. Return code was ${mconf_res}.")
71            else()
72                string(STRIP "${mconf_out}" mconf_out)
73                set(mconf_expected_ver "mconf-v4.6.0.0-idf-20190628-win32")
74                if(NOT ${mconf_out} STREQUAL "mconf-idf version ${mconf_expected_ver}")
75                    message(WARNING "Unexpected ${mconf_out}. Expected ${mconf_expected_ver}. "
76                                    "Please check the ESP-IDF Getting Started guide for version "
77                                    "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}.${IDF_VERSION_PATCH} "
78                                    "to correct this issue")
79                else()
80                    message(STATUS "${mconf_out}")   # prints: mconf-idf version ....
81                endif()
82            endif()
83            if(WINPTY)
84                set(MCONF "\"${WINPTY}\" \"${MCONF}\"")
85            endif()
86        endif()
87        idf_build_set_property(__MCONF ${MCONF})
88        idf_build_set_property(__MENUCONFIG_DEPENDS "${menuconfig_depends}")
89    endif()
90
91    idf_build_get_property(idf_path IDF_PATH)
92    idf_build_set_property(__ROOT_KCONFIG ${idf_path}/Kconfig)
93    idf_build_set_property(__ROOT_SDKCONFIG_RENAME ${idf_path}/sdkconfig.rename)
94    idf_build_set_property(__OUTPUT_SDKCONFIG 1)
95endfunction()
96
97#
98# Initialize Kconfig-related properties for components.
99# This function assumes that all basic properties of the components have been
100# set prior to calling it.
101#
102function(__kconfig_component_init component_target)
103    __component_get_property(component_dir ${component_target} COMPONENT_DIR)
104    file(GLOB kconfig "${component_dir}/Kconfig")
105    list(SORT kconfig)
106    __component_set_property(${component_target} KCONFIG "${kconfig}")
107    file(GLOB kconfig "${component_dir}/Kconfig.projbuild")
108    list(SORT kconfig)
109    __component_set_property(${component_target} KCONFIG_PROJBUILD "${kconfig}")
110    file(GLOB sdkconfig_rename "${component_dir}/sdkconfig.rename")
111    list(SORT sdkconfig_rename)
112    __component_set_property(${component_target} SDKCONFIG_RENAME "${sdkconfig_rename}")
113endfunction()
114
115#
116# Add bootloader components Kconfig and Kconfig.projbuild files to BOOTLOADER_KCONFIG
117# and BOOTLOADER_KCONFIGS_PROJ properties respectively.
118#
119function(__kconfig_bootloader_component_add component_dir)
120    idf_build_get_property(bootloader_kconfigs BOOTLOADER_KCONFIGS)
121    idf_build_get_property(bootloader_kconfigs_proj BOOTLOADER_KCONFIGS_PROJ)
122
123    file(GLOB kconfig "${component_dir}/Kconfig")
124    list(SORT kconfig)
125    if(EXISTS "${kconfig}" AND NOT IS_DIRECTORY "${kconfig}")
126        list(APPEND bootloader_kconfigs "${kconfig}")
127    endif()
128
129    file(GLOB kconfig "${component_dir}/Kconfig.projbuild")
130    list(SORT kconfig)
131    if(EXISTS "${kconfig}" AND NOT IS_DIRECTORY "${kconfig}")
132        list(APPEND bootloader_kconfigs_proj "${kconfig}")
133    endif()
134
135    idf_build_set_property(BOOTLOADER_KCONFIGS "${bootloader_kconfigs}")
136    idf_build_set_property(BOOTLOADER_KCONFIGS_PROJ "${bootloader_kconfigs_proj}")
137endfunction()
138
139
140#
141# Generate the config files and create config related targets and configure
142# dependencies.
143#
144function(__kconfig_generate_config sdkconfig sdkconfig_defaults)
145    # List all Kconfig and Kconfig.projbuild in known components
146    idf_build_get_property(component_targets __COMPONENT_TARGETS)
147    idf_build_get_property(build_component_targets __BUILD_COMPONENT_TARGETS)
148    foreach(component_target ${component_targets})
149        if(component_target IN_LIST build_component_targets)
150            __component_get_property(kconfig ${component_target} KCONFIG)
151            __component_get_property(kconfig_projbuild ${component_target} KCONFIG_PROJBUILD)
152            __component_get_property(sdkconfig_rename ${component_target} SDKCONFIG_RENAME)
153            if(kconfig)
154                list(APPEND kconfigs ${kconfig})
155            endif()
156            if(kconfig_projbuild)
157                list(APPEND kconfig_projbuilds ${kconfig_projbuild})
158            endif()
159            if(sdkconfig_rename)
160                list(APPEND sdkconfig_renames ${sdkconfig_rename})
161            endif()
162        endif()
163    endforeach()
164
165    # Take into account bootloader components configuration files
166    idf_build_get_property(bootloader_kconfigs BOOTLOADER_KCONFIGS)
167    idf_build_get_property(bootloader_kconfigs_proj BOOTLOADER_KCONFIGS_PROJ)
168    if(bootloader_kconfigs)
169        list(APPEND kconfigs "${bootloader_kconfigs}")
170    endif()
171    if(bootloader_kconfigs_proj)
172        list(APPEND kconfig_projbuilds "${bootloader_kconfigs_proj}")
173    endif()
174
175    # Store the list version of kconfigs and kconfig_projbuilds
176    idf_build_set_property(KCONFIGS "${kconfigs}")
177    idf_build_set_property(KCONFIG_PROJBUILDS "${kconfig_projbuilds}")
178
179    idf_build_get_property(idf_target IDF_TARGET)
180    idf_build_get_property(idf_path IDF_PATH)
181    idf_build_get_property(idf_env_fpga __IDF_ENV_FPGA)
182
183    string(REPLACE ";" " " kconfigs "${kconfigs}")
184    string(REPLACE ";" " " kconfig_projbuilds "${kconfig_projbuilds}")
185    string(REPLACE ";" " " sdkconfig_renames "${sdkconfig_renames}")
186
187    # These are the paths for files which will contain the generated "source" lines for COMPONENT_KCONFIGS and
188    # COMPONENT_KCONFIGS_PROJBUILD
189    set(kconfigs_projbuild_path "${CMAKE_CURRENT_BINARY_DIR}/kconfigs_projbuild.in")
190    set(kconfigs_path "${CMAKE_CURRENT_BINARY_DIR}/kconfigs.in")
191
192    # Place config-related environment arguments into config.env file
193    # to work around command line length limits for execute_process
194    # on Windows & CMake < 3.11
195    set(config_env_path "${CMAKE_CURRENT_BINARY_DIR}/config.env")
196    configure_file("${idf_path}/tools/kconfig_new/config.env.in" ${config_env_path})
197    idf_build_set_property(CONFIG_ENV_PATH ${config_env_path})
198
199    if(sdkconfig_defaults)
200        foreach(sdkconfig_default ${sdkconfig_defaults})
201            list(APPEND defaults_arg --defaults "${sdkconfig_default}")
202        endforeach()
203    endif()
204
205    if(sdkconfig_defaults)
206        foreach(sdkconfig_default ${sdkconfig_defaults})
207            if(EXISTS "${sdkconfig_default}.${idf_target}")
208                list(APPEND defaults_arg --defaults "${sdkconfig_default}.${idf_target}")
209            endif()
210        endforeach()
211    endif()
212
213    idf_build_get_property(root_kconfig __ROOT_KCONFIG)
214    idf_build_get_property(root_sdkconfig_rename __ROOT_SDKCONFIG_RENAME)
215    idf_build_get_property(python PYTHON)
216
217    set(prepare_kconfig_files_command
218        ${python} ${idf_path}/tools/kconfig_new/prepare_kconfig_files.py
219        --env-file ${config_env_path})
220
221    set(confgen_basecommand
222        ${python} ${idf_path}/tools/kconfig_new/confgen.py
223        --kconfig ${root_kconfig}
224        --sdkconfig-rename ${root_sdkconfig_rename}
225        --config ${sdkconfig}
226        ${defaults_arg}
227        --env-file ${config_env_path})
228
229    idf_build_get_property(build_dir BUILD_DIR)
230    set(config_dir ${build_dir}/config)
231    file(MAKE_DIRECTORY "${config_dir}")
232
233    message(STATUS "Project sdkconfig file ${sdkconfig}")
234
235    # Generate the config outputs
236    set(sdkconfig_cmake ${config_dir}/sdkconfig.cmake)
237    set(sdkconfig_header ${config_dir}/sdkconfig.h)
238    set(sdkconfig_json ${config_dir}/sdkconfig.json)
239    set(sdkconfig_json_menus ${config_dir}/kconfig_menus.json)
240
241    idf_build_get_property(output_sdkconfig __OUTPUT_SDKCONFIG)
242    if(output_sdkconfig)
243        execute_process(
244            COMMAND ${prepare_kconfig_files_command})
245        execute_process(
246            COMMAND ${confgen_basecommand}
247            --output header ${sdkconfig_header}
248            --output cmake ${sdkconfig_cmake}
249            --output json ${sdkconfig_json}
250            --output json_menus ${sdkconfig_json_menus}
251            --output config ${sdkconfig}
252            RESULT_VARIABLE config_result)
253    else()
254        execute_process(
255            COMMAND ${prepare_kconfig_files_command})
256        execute_process(
257            COMMAND ${confgen_basecommand}
258            --output header ${sdkconfig_header}
259            --output cmake ${sdkconfig_cmake}
260            --output json ${sdkconfig_json}
261            --output json_menus ${sdkconfig_json_menus}
262            RESULT_VARIABLE config_result)
263    endif()
264
265    if(config_result)
266        message(FATAL_ERROR "Failed to run confgen.py (${confgen_basecommand}). Error ${config_result}")
267    endif()
268
269    # Add the generated config header to build specifications.
270    idf_build_set_property(INCLUDE_DIRECTORIES ${config_dir} APPEND)
271
272    # When sdkconfig file changes in the future, trigger a cmake run
273    set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${sdkconfig}")
274
275    # Ditto if either of the generated files are missing/modified (this is a bit irritating as it means
276    # you can't edit these manually without them being regenerated, but I don't know of a better way...)
277    set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${sdkconfig_header}")
278    set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${sdkconfig_cmake}")
279
280    # Or if the config generation tool changes
281    set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${idf_path}/tools/kconfig_new/confgen.py")
282
283    set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
284                ADDITIONAL_MAKE_CLEAN_FILES "${sdkconfig_header}" "${sdkconfig_cmake}")
285
286    idf_build_set_property(SDKCONFIG_HEADER ${sdkconfig_header})
287    idf_build_set_property(SDKCONFIG_JSON ${sdkconfig_json})
288    idf_build_set_property(SDKCONFIG_CMAKE ${sdkconfig_cmake})
289    idf_build_set_property(SDKCONFIG_JSON_MENUS ${sdkconfig_json_menus})
290    idf_build_set_property(CONFIG_DIR ${config_dir})
291
292    if(CMAKE_HOST_WIN32 AND DEFINED ENV{MSYSTEM})
293        idf_build_get_property(menuconfig_depends __MENUCONFIG_DEPENDS)
294        idf_build_get_property(mconf __MCONF)
295
296        set(MENUCONFIG_CMD ${mconf})
297    else()
298        set(MENUCONFIG_CMD ${python} -m menuconfig)
299        set(TERM_CHECK_CMD ${python} ${idf_path}/tools/check_term.py)
300    endif()
301
302    # Generate the menuconfig target
303    add_custom_target(menuconfig
304        ${menuconfig_depends}
305        # create any missing config file, with defaults if necessary
306        COMMAND ${prepare_kconfig_files_command}
307        COMMAND ${confgen_basecommand}
308        --env "IDF_TARGET=${idf_target}"
309        --env "IDF_ENV_FPGA=${idf_env_fpga}"
310        --dont-write-deprecated
311        --output config ${sdkconfig}
312        COMMAND ${TERM_CHECK_CMD}
313        COMMAND ${CMAKE_COMMAND} -E env
314        "COMPONENT_KCONFIGS_SOURCE_FILE=${kconfigs_path}"
315        "COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE=${kconfigs_projbuild_path}"
316        "IDF_CMAKE=y"
317        "KCONFIG_CONFIG=${sdkconfig}"
318        "IDF_TARGET=${idf_target}"
319        "IDF_ENV_FPGA=${idf_env_fpga}"
320        ${MENUCONFIG_CMD} ${root_kconfig}
321        # VERBATIM cannot be used here because it cannot handle ${mconf}="winpty mconf-idf" and the escaping must be
322        # done manually
323        USES_TERMINAL
324        # additional run of confgen esures that the deprecated options will be inserted into sdkconfig (for backward
325        # compatibility)
326        COMMAND ${confgen_basecommand}
327        --env "IDF_TARGET=${idf_target}"
328        --env "IDF_ENV_FPGA=${idf_env_fpga}"
329        --output config ${sdkconfig}
330        )
331
332    # Custom target to run confserver.py from the build tool
333    add_custom_target(confserver
334        COMMAND ${prepare_kconfig_files_command}
335        COMMAND ${PYTHON} ${IDF_PATH}/tools/kconfig_new/confserver.py
336        --env-file ${config_env_path}
337        --kconfig ${IDF_PATH}/Kconfig
338        --sdkconfig-rename ${root_sdkconfig_rename}
339        --config ${sdkconfig}
340        VERBATIM
341        USES_TERMINAL)
342endfunction()
343