mirror of
https://github.com/espressif/ESP8266_RTOS_SDK.git
synced 2025-05-21 17:16:29 +08:00
Merge branch 'chore/add_cmake_tools' into 'master'
Add cmake tools See merge request sdk/ESP8266_RTOS_SDK!123
This commit is contained in:
@ -1,6 +1,11 @@
|
||||
README
|
||||
|
||||
The SDK uses 'kconfig', 'idf_monitor.py' of esp-idf and information of esp-idf is following:
|
||||
Information of 'idf_monitor.py' is following:
|
||||
|
||||
URL: https://github.com/espressif/esp-idf
|
||||
tag: v3.1-dev
|
||||
|
||||
Information of 'cmake', 'kconfig', 'kconfig_new' and 'idf.py' is following:
|
||||
|
||||
commit: 48c3ad37
|
||||
|
||||
|
165
tools/cmake/components.cmake
Normal file
165
tools/cmake/components.cmake
Normal file
@ -0,0 +1,165 @@
|
||||
# Given a list of components in 'component_paths', filter only paths to the components
|
||||
# mentioned in 'components' and return as a list in 'result_paths'
|
||||
function(components_get_paths component_paths components result_paths)
|
||||
set(result "")
|
||||
foreach(path ${component_paths})
|
||||
get_filename_component(name "${path}" NAME)
|
||||
if("${name}" IN_LIST components)
|
||||
list(APPEND result "${name}")
|
||||
endif()
|
||||
endforeach()
|
||||
set("${result_path}" "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Add a component to the build, using the COMPONENT variables defined
|
||||
# in the parent
|
||||
#
|
||||
function(register_component)
|
||||
get_filename_component(component_dir ${CMAKE_CURRENT_LIST_FILE} DIRECTORY)
|
||||
get_filename_component(component ${component_dir} NAME)
|
||||
|
||||
spaces2list(COMPONENT_SRCDIRS)
|
||||
spaces2list(COMPONENT_ADD_INCLUDEDIRS)
|
||||
|
||||
# Add to COMPONENT_SRCS by globbing in COMPONENT_SRCDIRS
|
||||
if(NOT COMPONENT_SRCS)
|
||||
foreach(dir ${COMPONENT_SRCDIRS})
|
||||
get_filename_component(abs_dir ${dir} ABSOLUTE BASE_DIR ${component_dir})
|
||||
if(NOT IS_DIRECTORY ${abs_dir})
|
||||
message(FATAL_ERROR "${CMAKE_CURRENT_LIST_FILE}: COMPONENT_SRCDIRS entry '${dir}' does not exist")
|
||||
endif()
|
||||
|
||||
file(GLOB matches "${abs_dir}/*.c" "${abs_dir}/*.cpp" "${abs_dir}/*.S")
|
||||
if(matches)
|
||||
list(SORT matches)
|
||||
set(COMPONENT_SRCS "${COMPONENT_SRCS};${matches}")
|
||||
else()
|
||||
message(FATAL_ERROR "${CMAKE_CURRENT_LIST_FILE}: COMPONENT_SRCDIRS entry '${dir}' has no source files")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# add as a PUBLIC library (if there are source files) or INTERFACE (if header only)
|
||||
if(COMPONENT_SRCS OR embed_binaries)
|
||||
add_library(${component} STATIC ${COMPONENT_SRCS})
|
||||
set(include_type PUBLIC)
|
||||
else()
|
||||
add_library(${component} INTERFACE) # header-only component
|
||||
set(include_type INTERFACE)
|
||||
endif()
|
||||
|
||||
# binaries to embed directly in library
|
||||
spaces2list(COMPONENT_EMBED_FILES)
|
||||
spaces2list(COMPONENT_EMBED_TXTFILES)
|
||||
foreach(embed_data ${COMPONENT_EMBED_FILES} ${COMPONENT_EMBED_TXTFILES})
|
||||
if(embed_data IN_LIST COMPONENT_EMBED_TXTFILES)
|
||||
set(embed_type "TEXT")
|
||||
else()
|
||||
set(embed_type "BINARY")
|
||||
endif()
|
||||
target_add_binary_data("${component}" "${embed_data}" "${embed_type}")
|
||||
endforeach()
|
||||
|
||||
# add component public includes
|
||||
foreach(include_dir ${COMPONENT_ADD_INCLUDEDIRS})
|
||||
get_filename_component(abs_dir ${include_dir} ABSOLUTE BASE_DIR ${component_dir})
|
||||
if(NOT IS_DIRECTORY ${abs_dir})
|
||||
message(FATAL_ERROR "${CMAKE_CURRENT_LIST_FILE}: "
|
||||
"COMPONENT_ADD_INCLUDEDIRS entry '${include_dir}' not found")
|
||||
endif()
|
||||
target_include_directories(${component} ${include_type} ${abs_dir})
|
||||
endforeach()
|
||||
|
||||
# add component private includes
|
||||
foreach(include_dir ${COMPONENT_PRIV_INCLUDEDIRS})
|
||||
if(${include_type} STREQUAL INTERFACE)
|
||||
message(FATAL_ERROR "${CMAKE_CURRENT_LIST_FILE} "
|
||||
"sets no component source files but sets COMPONENT_PRIV_INCLUDEDIRS")
|
||||
endif()
|
||||
|
||||
get_filename_component(abs_dir ${include_dir} ABSOLUTE BASE_DIR ${component_dir})
|
||||
if(NOT IS_DIRECTORY ${abs_dir})
|
||||
message(FATAL_ERROR "${CMAKE_CURRENT_LIST_FILE}: "
|
||||
"COMPONENT_PRIV_INCLUDEDIRS entry '${include_dir}' does not exist")
|
||||
endif()
|
||||
target_include_directories(${component} PRIVATE ${abs_dir})
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
function(register_config_only_component)
|
||||
get_filename_component(component_dir ${CMAKE_CURRENT_LIST_FILE} DIRECTORY)
|
||||
get_filename_component(component ${component_dir} NAME)
|
||||
|
||||
# No-op for now...
|
||||
endfunction()
|
||||
|
||||
function(add_component_dependencies target dep dep_type)
|
||||
get_target_property(target_type ${target} TYPE)
|
||||
get_target_property(target_imported ${target} IMPORTED)
|
||||
|
||||
if(${target_type} STREQUAL STATIC_LIBRARY OR ${target_type} STREQUAL EXECUTABLE)
|
||||
if(TARGET ${dep})
|
||||
# Add all compile options exported by dep into target
|
||||
target_include_directories(${target} ${dep_type}
|
||||
$<TARGET_PROPERTY:${dep},INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
target_compile_definitions(${target} ${dep_type}
|
||||
$<TARGET_PROPERTY:${dep},INTERFACE_COMPILE_DEFINITIONS>)
|
||||
target_compile_options(${target} ${dep_type}
|
||||
$<TARGET_PROPERTY:${dep},INTERFACE_COMPILE_OPTIONS>)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(components_finish_registration)
|
||||
|
||||
# have the executable target depend on all components in the build
|
||||
set_target_properties(${CMAKE_PROJECT_NAME}.elf PROPERTIES INTERFACE_COMPONENT_REQUIRES "${BUILD_COMPONENTS}")
|
||||
|
||||
spaces2list(COMPONENT_REQUIRES_COMMON)
|
||||
|
||||
# each component should see the include directories of its requirements
|
||||
#
|
||||
# (we can't do this until all components are registered and targets exist in cmake, as we have
|
||||
# a circular requirements graph...)
|
||||
foreach(a ${BUILD_COMPONENTS})
|
||||
if(TARGET ${a})
|
||||
get_component_requirements("${a}" a_deps a_priv_deps)
|
||||
list(APPEND a_priv_deps ${COMPONENT_REQUIRES_COMMON})
|
||||
foreach(b ${a_deps})
|
||||
add_component_dependencies(${a} ${b} PUBLIC)
|
||||
endforeach()
|
||||
|
||||
foreach(b ${a_priv_deps})
|
||||
add_component_dependencies(${a} ${b} PRIVATE)
|
||||
endforeach()
|
||||
|
||||
get_target_property(a_type ${a} TYPE)
|
||||
if(${a_type} MATCHES .+_LIBRARY)
|
||||
set(COMPONENT_LIBRARIES "${COMPONENT_LIBRARIES};${a}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Add each component library's link-time dependencies (which are otherwise ignored) to the executable
|
||||
# LINK_DEPENDS in order to trigger a re-link when needed (on Ninja/Makefile generators at least).
|
||||
# (maybe this should probably be something CMake does, but it doesn't do it...)
|
||||
foreach(component ${BUILD_COMPONENTS})
|
||||
if(TARGET ${component})
|
||||
get_target_property(imported ${component} IMPORTED)
|
||||
get_target_property(type ${component} TYPE)
|
||||
if(NOT imported)
|
||||
if(${type} STREQUAL STATIC_LIBRARY OR ${type} STREQUAL EXECUTABLE)
|
||||
get_target_property(link_depends "${component}" LINK_DEPENDS)
|
||||
if(link_depends)
|
||||
set_property(TARGET ${CMAKE_PROJECT_NAME}.elf APPEND PROPERTY LINK_DEPENDS "${link_depends}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}.elf ${COMPONENT_LIBRARIES})
|
||||
|
||||
message(STATUS "Component libraries: ${COMPONENT_LIBRARIES}")
|
||||
|
||||
endfunction()
|
219
tools/cmake/convert_to_cmake.py
Executable file
219
tools/cmake/convert_to_cmake.py
Executable file
@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Command line tool to convert simple ESP-IDF Makefile & component.mk files to
|
||||
# CMakeLists.txt files
|
||||
#
|
||||
import argparse
|
||||
import subprocess
|
||||
import re
|
||||
import os.path
|
||||
import glob
|
||||
import sys
|
||||
|
||||
debug = False
|
||||
|
||||
def get_make_variables(path, makefile="Makefile", expected_failure=False, variables={}):
|
||||
"""
|
||||
Given the path to a Makefile of some kind, return a dictionary of all variables defined in this Makefile
|
||||
|
||||
Uses 'make' to parse the Makefile syntax, so we don't have to!
|
||||
|
||||
Overrides IDF_PATH= to avoid recursively evaluating the entire project Makefile structure.
|
||||
"""
|
||||
variable_setters = [ ("%s=%s" % (k,v)) for (k,v) in variables.items() ]
|
||||
|
||||
cmdline = ["make", "-rpn", "-C", path, "-f", makefile ] + variable_setters
|
||||
if debug:
|
||||
print("Running %s..." % (" ".join(cmdline)))
|
||||
|
||||
p = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, stderr) = p.communicate("\n")
|
||||
|
||||
if (not expected_failure) and p.returncode != 0:
|
||||
raise RuntimeError("Unexpected make failure, result %d" % p.returncode)
|
||||
|
||||
if debug:
|
||||
print("Make stdout:")
|
||||
print(output)
|
||||
print("Make stderr:")
|
||||
print(stderr)
|
||||
|
||||
next_is_makefile = False # is the next line a makefile variable?
|
||||
result = {}
|
||||
BUILT_IN_VARS = set(["MAKEFILE_LIST", "SHELL", "CURDIR", "MAKEFLAGS"])
|
||||
|
||||
for line in output.decode().split("\n"):
|
||||
if line.startswith("# makefile"): # this line appears before any variable defined in the makefile itself
|
||||
next_is_makefile = True
|
||||
elif next_is_makefile:
|
||||
next_is_makefile = False
|
||||
m = re.match(r"(?P<var>[^ ]+) :?= (?P<val>.+)", line)
|
||||
if m is not None:
|
||||
if not m.group("var") in BUILT_IN_VARS:
|
||||
result[m.group("var")] = m.group("val").strip()
|
||||
|
||||
return result
|
||||
|
||||
def get_component_variables(project_path, component_path):
|
||||
make_vars = get_make_variables(component_path,
|
||||
os.path.join(os.environ["IDF_PATH"],
|
||||
"make",
|
||||
"component_wrapper.mk"),
|
||||
expected_failure=True,
|
||||
variables = {
|
||||
"COMPONENT_MAKEFILE" : os.path.join(component_path, "component.mk"),
|
||||
"COMPONENT_NAME" : os.path.basename(component_path),
|
||||
"PROJECT_PATH": project_path,
|
||||
})
|
||||
|
||||
if "COMPONENT_OBJS" in make_vars: # component.mk specifies list of object files
|
||||
# Convert to sources
|
||||
def find_src(obj):
|
||||
obj = os.path.splitext(obj)[0]
|
||||
for ext in [ "c", "cpp", "S" ]:
|
||||
if os.path.exists(os.path.join(component_path, obj) + "." + ext):
|
||||
return obj + "." + ext
|
||||
print("WARNING: Can't find source file for component %s COMPONENT_OBJS %s" % (component_path, obj))
|
||||
return None
|
||||
|
||||
srcs = []
|
||||
for obj in make_vars["COMPONENT_OBJS"].split(" "):
|
||||
src = find_src(obj)
|
||||
if src is not None:
|
||||
srcs.append(src)
|
||||
make_vars["COMPONENT_SRCS"] = " ".join(srcs)
|
||||
else: # Use COMPONENT_SRCDIRS
|
||||
make_vars["COMPONENT_SRCDIRS"] = make_vars.get("COMPONENT_SRCDIRS", ".")
|
||||
|
||||
make_vars["COMPONENT_ADD_INCLUDEDIRS"] = make_vars.get("COMPONENT_ADD_INCLUDEDIRS", "include")
|
||||
|
||||
return make_vars
|
||||
|
||||
|
||||
def convert_project(project_path):
|
||||
if not os.path.exists(project_path):
|
||||
raise RuntimeError("Project directory '%s' not found" % project_path)
|
||||
if not os.path.exists(os.path.join(project_path, "Makefile")):
|
||||
raise RuntimeError("Directory '%s' doesn't contain a project Makefile" % project_path)
|
||||
|
||||
project_cmakelists = os.path.join(project_path, "CMakeLists.txt")
|
||||
if os.path.exists(project_cmakelists):
|
||||
raise RuntimeError("This project already has a CMakeLists.txt file")
|
||||
|
||||
project_vars = get_make_variables(project_path, expected_failure=True)
|
||||
if not "PROJECT_NAME" in project_vars:
|
||||
raise RuntimeError("PROJECT_NAME does not appear to be defined in IDF project Makefile at %s" % project_path)
|
||||
|
||||
component_paths = project_vars["COMPONENT_PATHS"].split(" ")
|
||||
|
||||
# "main" component is made special in cmake, so extract it from the component_paths list
|
||||
try:
|
||||
main_component_path = [ p for p in component_paths if os.path.basename(p) == "main" ][0]
|
||||
if debug:
|
||||
print("Found main component %s" % main_component_path)
|
||||
main_vars = get_component_variables(project_path, main_component_path)
|
||||
except IndexError:
|
||||
print("WARNING: Project has no 'main' component, but CMake-based system requires at least one file in MAIN_SRCS...")
|
||||
main_vars = { "COMPONENT_SRCS" : ""} # dummy for MAIN_SRCS
|
||||
|
||||
# Remove main component from list of components we're converting to cmake
|
||||
component_paths = [ p for p in component_paths if os.path.basename(p) != "main" ]
|
||||
|
||||
# Convert components as needed
|
||||
for p in component_paths:
|
||||
convert_component(project_path, p)
|
||||
|
||||
# Look up project variables before we start writing the file, so nothing
|
||||
# is created if there is an error
|
||||
|
||||
main_srcs = main_vars["COMPONENT_SRCS"].split(" ")
|
||||
# convert from component-relative to absolute paths
|
||||
main_srcs = [ os.path.normpath(os.path.join(main_component_path, m)) for m in main_srcs ]
|
||||
# convert to make relative to the project directory
|
||||
main_srcs = [ os.path.relpath(m, project_path) for m in main_srcs ]
|
||||
|
||||
project_name = project_vars["PROJECT_NAME"]
|
||||
|
||||
# Generate the project CMakeLists.txt file
|
||||
with open(project_cmakelists, "w") as f:
|
||||
f.write("""
|
||||
# (Automatically converted from project Makefile by convert_to_cmake.py.)
|
||||
|
||||
# The following four lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
""")
|
||||
f.write("set(MAIN_SRCS %s)\n" % " ".join(main_srcs))
|
||||
f.write("""
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
""")
|
||||
f.write("project(%s)\n" % project_name)
|
||||
|
||||
print("Converted project %s" % project_cmakelists)
|
||||
|
||||
def convert_component(project_path, component_path):
|
||||
if debug:
|
||||
print("Converting %s..." % (component_path))
|
||||
cmakelists_path = os.path.join(component_path, "CMakeLists.txt")
|
||||
if os.path.exists(cmakelists_path):
|
||||
print("Skipping already-converted component %s..." % cmakelists_path)
|
||||
return
|
||||
v = get_component_variables(project_path, component_path)
|
||||
|
||||
# Look up all the variables before we start writing the file, so it's not
|
||||
# created if there's an erro
|
||||
component_srcs = v.get("COMPONENT_SRCS", None)
|
||||
component_srcdirs = None
|
||||
if component_srcs is not None:
|
||||
# see if we should be using COMPONENT_SRCS or COMPONENT_SRCDIRS, if COMPONENT_SRCS is everything in SRCDIRS
|
||||
component_allsrcs = []
|
||||
for d in v.get("COMPONENT_SRCDIRS", "").split(" "):
|
||||
component_allsrcs += glob.glob(os.path.normpath(os.path.join(component_path, d, "*.[cS]")))
|
||||
component_allsrcs += glob.glob(os.path.normpath(os.path.join(component_path, d, "*.cpp")))
|
||||
abs_component_srcs = [os.path.normpath(os.path.join(component_path, p)) for p in component_srcs.split(" ")]
|
||||
if set(component_allsrcs) == set(abs_component_srcs):
|
||||
component_srcdirs = v.get("COMPONENT_SRCDIRS")
|
||||
|
||||
component_add_includedirs = v["COMPONENT_ADD_INCLUDEDIRS"]
|
||||
cflags = v.get("CFLAGS", None)
|
||||
|
||||
with open(cmakelists_path, "w") as f:
|
||||
f.write("set(COMPONENT_ADD_INCLUDEDIRS %s)\n\n" % component_add_includedirs)
|
||||
|
||||
f.write("# Edit following two lines to set component requirements (see docs)\n")
|
||||
f.write("set(COMPONENT_REQUIRES "")\n")
|
||||
f.write("set(COMPONENT_PRIV_REQUIRES "")\n\n")
|
||||
|
||||
if component_srcdirs is not None:
|
||||
f.write("set(COMPONENT_SRCDIRS %s)\n\n" % component_srcdirs)
|
||||
f.write("register_component()\n")
|
||||
elif component_srcs is not None:
|
||||
f.write("set(COMPONENT_SRCS %s)\n\n" % component_srcs)
|
||||
f.write("register_component()\n")
|
||||
else:
|
||||
f.write("register_config_only_component()\n")
|
||||
if cflags is not None:
|
||||
f.write("component_compile_options(%s)\n" % cflags)
|
||||
|
||||
print("Converted %s" % cmakelists_path)
|
||||
|
||||
|
||||
def main():
|
||||
global debug
|
||||
|
||||
parser = argparse.ArgumentParser(description='convert_to_cmake.py - ESP-IDF Project Makefile to CMakeLists.txt converter', prog='convert_to_cmake')
|
||||
|
||||
parser.add_argument('--debug', help='Display debugging output',
|
||||
action='store_true')
|
||||
|
||||
parser.add_argument('project', help='Path to project to convert (defaults to CWD)', default=os.getcwd(), metavar='project path', nargs='?')
|
||||
|
||||
args = parser.parse_args()
|
||||
debug = args.debug
|
||||
print("Converting %s..." % args.project)
|
||||
convert_project(args.project)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
32
tools/cmake/crosstool_version_check.cmake
Normal file
32
tools/cmake/crosstool_version_check.cmake
Normal file
@ -0,0 +1,32 @@
|
||||
# Function to check the toolchain used the expected version
|
||||
# of crosstool, and warn otherwise
|
||||
|
||||
set(ctng_version_warning "Check Getting Started documentation or proceed at own risk.")
|
||||
|
||||
function(gcc_version_check expected_gcc_version)
|
||||
if(NOT "${CMAKE_C_COMPILER_VERSION}" STREQUAL "${expected_gcc_version}")
|
||||
message(WARNING "Xtensa toolchain ${CMAKE_C_COMPILER} version ${CMAKE_C_COMPILER_VERSION} "
|
||||
"is not the supported version ${expected_gcc_version}. ${ctng_version_warning}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(crosstool_version_check expected_ctng_version)
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -v
|
||||
ERROR_VARIABLE toolchain_stderr
|
||||
OUTPUT_QUIET)
|
||||
|
||||
string(REGEX MATCH "crosstool-ng-[0-9a-g\\.-]+" ctng_version "${toolchain_stderr}")
|
||||
string(REPLACE "crosstool-ng-" "" ctng_version "${ctng_version}")
|
||||
# We use FIND to match version instead of STREQUAL because some toolchains are built
|
||||
# with longer git hash strings than others. This will match any version which starts with
|
||||
# the expected version string.
|
||||
string(FIND "${ctng_version}" "${expected_ctng_version}" found_expected_version)
|
||||
if(NOT ctng_version)
|
||||
message(WARNING "Xtensa toolchain ${CMAKE_C_COMPILER} does not appear to be built with crosstool-ng. "
|
||||
"${ctng_version_warning}")
|
||||
elseif(found_expected_version EQUAL -1)
|
||||
message(WARNING "Xtensa toolchain ${CMAKE_C_COMPILER} crosstool-ng version ${ctng_version} "
|
||||
"doesn't match supported version ${expected_ctng_version}. ${ctng_version_warning}")
|
||||
endif()
|
||||
endfunction()
|
61
tools/cmake/git_submodules.cmake
Normal file
61
tools/cmake/git_submodules.cmake
Normal file
@ -0,0 +1,61 @@
|
||||
find_package(Git)
|
||||
|
||||
if(NOT GIT_FOUND)
|
||||
message(WARNING "Git executable was not found. Git submodule checks will not be executed. "
|
||||
"If you have any build issues at all, start by adding git executable to the PATH and "
|
||||
"rerun cmake to not see this warning again.")
|
||||
|
||||
function(git_submodule_check root_path)
|
||||
# no-op
|
||||
endfunction()
|
||||
else()
|
||||
|
||||
function(git_submodule_check root_path)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} submodule status
|
||||
WORKING_DIRECTORY ${root_path}
|
||||
OUTPUT_VARIABLE submodule_status
|
||||
)
|
||||
|
||||
# git submodule status output not guaranteed to be stable,
|
||||
# may need to check GIT_VERSION_STRING and do some fiddling in the
|
||||
# future...
|
||||
|
||||
lines2list(submodule_status)
|
||||
|
||||
foreach(line ${submodule_status})
|
||||
string(REGEX MATCH "(.)[0-9a-f]+ ([^\( ]+) ?" _ignored "${line}")
|
||||
set(status "${CMAKE_MATCH_1}")
|
||||
set(submodule_path "${CMAKE_MATCH_2}")
|
||||
|
||||
if("${status}" STREQUAL "-") # missing submodule
|
||||
message(STATUS "Initialising new submodule ${submodule_path}...")
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive ${submodule_path}
|
||||
WORKING_DIRECTORY ${root_path}
|
||||
RESULT_VARIABLE git_result
|
||||
)
|
||||
if(git_result)
|
||||
message(FATAL_ERROR "Git submodule init failed for ${submodule_path}")
|
||||
endif()
|
||||
|
||||
elseif(NOT "${status}" STREQUAL " ")
|
||||
message(WARNING "Git submodule ${submodule_path} is out of date. "
|
||||
"Run 'git submodule update --init --recursive' to fix.")
|
||||
endif()
|
||||
|
||||
# Force a re-run of cmake if the submodule's .git file changes or is changed (ie accidental deinit)
|
||||
get_filename_component(submodule_abs_path ${submodule_path} ABSOLUTE BASE_DIR ${root_path})
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${submodule_abs_path}/.git)
|
||||
# same if the HEAD file in the submodule's directory changes (ie commit changes).
|
||||
# This will at least print the 'out of date' warning
|
||||
set(submodule_head "${root_path}/.git/modules/${submodule_path}/HEAD")
|
||||
if(EXISTS "${submodule_head}")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${submodule_head})
|
||||
endif()
|
||||
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
endif()
|
203
tools/cmake/idf_functions.cmake
Normal file
203
tools/cmake/idf_functions.cmake
Normal file
@ -0,0 +1,203 @@
|
||||
# Some IDF-specific functions and functions
|
||||
|
||||
include(crosstool_version_check)
|
||||
|
||||
#
|
||||
# Set some variables used by rest of the build
|
||||
#
|
||||
# Note at the time this macro is expanded, the config is not yet
|
||||
# loaded and the toolchain and project are not yet set
|
||||
#
|
||||
macro(idf_set_global_variables)
|
||||
# Note that CONFIG_xxx is not available when this function is called
|
||||
|
||||
set_default(EXTRA_COMPONENT_DIRS "")
|
||||
|
||||
# Commmon components, required by every component in the build
|
||||
#
|
||||
set_default(COMPONENT_REQUIRES_COMMON "cxx esp32 newlib freertos heap log soc")
|
||||
|
||||
# PROJECT_PATH has the path to the IDF project (top-level cmake directory)
|
||||
#
|
||||
# (cmake calls this CMAKE_SOURCE_DIR, keeping old name for compatibility.)
|
||||
set(PROJECT_PATH "${CMAKE_SOURCE_DIR}")
|
||||
|
||||
# Note: Unlike older build system, "main" is no longer a component. See build docs for details.
|
||||
set_default(COMPONENT_DIRS "${PROJECT_PATH}/components ${EXTRA_COMPONENT_DIRS} ${IDF_PATH}/components")
|
||||
spaces2list(COMPONENT_DIRS)
|
||||
|
||||
spaces2list(COMPONENTS)
|
||||
|
||||
# Tell cmake to drop executables in the top-level build dir
|
||||
set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}")
|
||||
|
||||
# path to idf.py tool
|
||||
set(IDFTOOL ${PYTHON} "${IDF_PATH}/tools/idf.py")
|
||||
endmacro()
|
||||
|
||||
# Add all the IDF global compiler & preprocessor options
|
||||
# (applied to all components). Some are config-dependent
|
||||
#
|
||||
# If you only want to set options for a particular component,
|
||||
# don't call or edit this function. TODO DESCRIBE WHAT TO DO INSTEAD
|
||||
#
|
||||
function(idf_set_global_compiler_options)
|
||||
add_definitions(-DESP_PLATFORM)
|
||||
add_definitions(-DHAVE_CONFIG_H)
|
||||
|
||||
if(CONFIG_OPTIMIZATION_LEVEL_RELEASE)
|
||||
add_compile_options(-Os)
|
||||
else()
|
||||
add_compile_options(-Og)
|
||||
endif()
|
||||
|
||||
add_c_compile_options(-std=gnu99)
|
||||
|
||||
add_cxx_compile_options(-std=gnu++11 -fno-rtti)
|
||||
|
||||
if(CONFIG_CXX_EXCEPTIONS)
|
||||
add_cxx_compile_options(-fexceptions)
|
||||
else()
|
||||
add_cxx_compile_options(-fno-exceptions)
|
||||
endif()
|
||||
|
||||
# Default compiler configuration
|
||||
add_compile_options(-ffunction-sections -fdata-sections -fstrict-volatile-bitfields -mlongcalls -nostdlib)
|
||||
|
||||
# Default warnings configuration
|
||||
add_compile_options(
|
||||
-Wall
|
||||
-Werror=all
|
||||
-Wno-error=unused-function
|
||||
-Wno-error=unused-but-set-variable
|
||||
-Wno-error=unused-variable
|
||||
-Wno-error=deprecated-declarations
|
||||
-Wextra
|
||||
-Wno-unused-parameter
|
||||
-Wno-sign-compare)
|
||||
add_c_compile_options(
|
||||
-Wno-old-style-declaration
|
||||
)
|
||||
|
||||
# Stack protection
|
||||
if(NOT BOOTLOADER_BUILD)
|
||||
if(CONFIG_STACK_CHECK_NORM)
|
||||
add_compile_options(-fstack-protector)
|
||||
elseif(CONFIG_STACK_CHECK_STRONG)
|
||||
add_compile_options(-fstack-protector-strong)
|
||||
elseif(CONFIG_STACK_CHECK_ALL)
|
||||
add_compile_options(-fstack-protector-all)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED)
|
||||
add_definitions(-DNDEBUG)
|
||||
endif()
|
||||
|
||||
# Always generate debug symbols (even in Release mode, these don't
|
||||
# go into the final binary so have no impact on size)
|
||||
add_compile_options(-ggdb)
|
||||
|
||||
add_compile_options("-I${CMAKE_BINARY_DIR}") # for sdkconfig.h
|
||||
|
||||
# Enable ccache if it's on the path
|
||||
if(NOT CCACHE_DISABLE)
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
message(STATUS "ccache will be used for faster builds")
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endfunction()
|
||||
|
||||
|
||||
# Verify the IDF environment is configured correctly (environment, toolchain, etc)
|
||||
function(idf_verify_environment)
|
||||
|
||||
if(NOT CMAKE_PROJECT_NAME)
|
||||
message(FATAL_ERROR "Internal error, IDF project.cmake should have set this variable already")
|
||||
endif()
|
||||
|
||||
# Check toolchain is configured properly in cmake
|
||||
if(NOT ( ${CMAKE_SYSTEM_NAME} STREQUAL "Generic" AND ${CMAKE_C_COMPILER} MATCHES xtensa))
|
||||
message(FATAL_ERROR "Internal error, toolchain has not been set correctly by project")
|
||||
endif()
|
||||
|
||||
#
|
||||
# Warn if the toolchain version doesn't match
|
||||
#
|
||||
# TODO: make these platform-specific for diff toolchains
|
||||
gcc_version_check("5.2.0")
|
||||
crosstool_version_check("1.22.0-80-g6c4433a")
|
||||
|
||||
endfunction()
|
||||
|
||||
# idf_add_executable
|
||||
#
|
||||
# Calls add_executable to add the final project executable
|
||||
# Adds .map & .bin file targets
|
||||
# Sets up flash-related targets
|
||||
function(idf_add_executable)
|
||||
set(exe_target ${PROJECT_NAME}.elf)
|
||||
|
||||
spaces2list(MAIN_SRCS)
|
||||
add_executable(${exe_target} "${MAIN_SRCS}")
|
||||
|
||||
add_map_file(${exe_target})
|
||||
endfunction()
|
||||
|
||||
|
||||
# add_map_file
|
||||
#
|
||||
# Set linker args for 'exe_target' to generate a linker Map file
|
||||
function(add_map_file exe_target)
|
||||
get_filename_component(basename ${exe_target} NAME_WE)
|
||||
set(mapfile "${basename}.map")
|
||||
target_link_libraries(${exe_target} "-Wl,--gc-sections -Wl,--cref -Wl,--Map=${mapfile} -Wl,--start-group")
|
||||
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
|
||||
ADDITIONAL_MAKE_CLEAN_FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${mapfile}")
|
||||
|
||||
# add size targets, depend on map file, run idf_size.py
|
||||
add_custom_target(size
|
||||
DEPENDS ${exe_target}
|
||||
COMMAND ${PYTHON} ${IDF_PATH}/tools/idf_size.py ${mapfile}
|
||||
)
|
||||
add_custom_target(size-files
|
||||
DEPENDS ${exe_target}
|
||||
COMMAND ${PYTHON} ${IDF_PATH}/tools/idf_size.py --files ${mapfile}
|
||||
)
|
||||
add_custom_target(size-components
|
||||
DEPENDS ${exe_target}
|
||||
COMMAND ${PYTHON} ${IDF_PATH}/tools/idf_size.py --archives ${mapfile}
|
||||
)
|
||||
|
||||
endfunction()
|
||||
|
||||
# component_compile_options
|
||||
#
|
||||
# Wrapper around target_compile_options that passes the component name
|
||||
function(component_compile_options)
|
||||
target_compile_options(${COMPONENT_NAME} PRIVATE ${ARGV})
|
||||
endfunction()
|
||||
|
||||
# component_compile_definitions
|
||||
#
|
||||
# Wrapper around target_compile_definitions that passes the component name
|
||||
function(component_compile_definitions)
|
||||
target_compile_definitions(${COMPONENT_NAME} PRIVATE ${ARGV})
|
||||
endfunction()
|
||||
|
||||
# idf_get_git_revision
|
||||
#
|
||||
# Set global IDF_VER to the git revision of ESP-IDF.
|
||||
#
|
||||
# Running git_describe() here automatically triggers rebuilds
|
||||
# if the ESP-IDF git version changes
|
||||
function(idf_get_git_revision)
|
||||
git_describe(IDF_VER "${IDF_PATH}")
|
||||
add_definitions(-DIDF_VER=\"${IDF_VER}\")
|
||||
git_submodule_check("${IDF_PATH}")
|
||||
set(IDF_VER ${IDF_VER} PARENT_SCOPE)
|
||||
endfunction()
|
130
tools/cmake/kconfig.cmake
Normal file
130
tools/cmake/kconfig.cmake
Normal file
@ -0,0 +1,130 @@
|
||||
include(ExternalProject)
|
||||
|
||||
macro(kconfig_set_variables)
|
||||
set_default(SDKCONFIG ${PROJECT_PATH}/sdkconfig)
|
||||
set(SDKCONFIG_HEADER ${CMAKE_BINARY_DIR}/sdkconfig.h)
|
||||
set(SDKCONFIG_CMAKE ${CMAKE_BINARY_DIR}/sdkconfig.cmake)
|
||||
set(SDKCONFIG_JSON ${CMAKE_BINARY_DIR}/sdkconfig.json)
|
||||
|
||||
set(ROOT_KCONFIG ${IDF_PATH}/Kconfig)
|
||||
|
||||
set_default(SDKCONFIG_DEFAULTS "${SDKCONFIG}.defaults")
|
||||
endmacro()
|
||||
|
||||
if(CMAKE_HOST_WIN32)
|
||||
# Prefer a prebuilt mconf on Windows
|
||||
find_program(WINPTY winpty)
|
||||
find_program(MCONF mconf)
|
||||
|
||||
if(NOT MCONF)
|
||||
find_program(NATIVE_GCC gcc)
|
||||
if(NOT NATIVE_GCC)
|
||||
message(FATAL_ERROR
|
||||
"Windows requires a prebuilt ESP-IDF-specific mconf for your platform "
|
||||
"on the PATH, or an MSYS2 version of gcc on the PATH to build mconf. "
|
||||
"Consult the setup docs for ESP-IDF on Windows.")
|
||||
endif()
|
||||
elseif(WINPTY)
|
||||
set(MCONF "${WINPTY}" "${MCONF}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT MCONF)
|
||||
# Use the existing Makefile to build mconf (out of tree) when needed
|
||||
#
|
||||
set(MCONF kconfig_bin/mconf)
|
||||
|
||||
externalproject_add(mconf
|
||||
SOURCE_DIR ${IDF_PATH}/tools/kconfig
|
||||
CONFIGURE_COMMAND ""
|
||||
BINARY_DIR "kconfig_bin"
|
||||
BUILD_COMMAND make -f ${IDF_PATH}/tools/kconfig/Makefile mconf
|
||||
BUILD_BYPRODUCTS ${MCONF}
|
||||
INSTALL_COMMAND ""
|
||||
EXCLUDE_FROM_ALL 1
|
||||
)
|
||||
set(menuconfig_depends DEPENDS mconf)
|
||||
endif()
|
||||
|
||||
# Find all Kconfig files for all components
|
||||
function(kconfig_process_config)
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/include/config")
|
||||
set(kconfigs)
|
||||
set(kconfigs_projbuild)
|
||||
|
||||
# Find Kconfig and Kconfig.projbuild for each component as applicable
|
||||
# if any of these change, cmake should rerun
|
||||
foreach(dir ${BUILD_COMPONENT_PATHS} "${CMAKE_SOURCE_DIR}/main")
|
||||
file(GLOB kconfig "${dir}/Kconfig")
|
||||
if(kconfig)
|
||||
set(kconfigs "${kconfigs} ${kconfig}")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${kconfig})
|
||||
endif()
|
||||
file(GLOB kconfig ${dir}/Kconfig.projbuild)
|
||||
if(kconfig)
|
||||
set(kconfigs_projbuild "${kconfigs_projbuild} ${kconfig}")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${kconfig})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(EXISTS ${SDKCONFIG_DEFAULTS})
|
||||
set(defaults_arg --defaults "${SDKCONFIG_DEFAULTS}")
|
||||
endif()
|
||||
|
||||
# Set these in the parent scope, so that they can be written to project_description.json
|
||||
set(kconfigs "${kconfigs}")
|
||||
set(COMPONENT_KCONFIGS "${kconfigs}" PARENT_SCOPE)
|
||||
set(COMPONENT_KCONFIGS_PROJBUILD "${kconfigs_projbuild}" PARENT_SCOPE)
|
||||
|
||||
set(confgen_basecommand
|
||||
${PYTHON} ${IDF_PATH}/tools/kconfig_new/confgen.py
|
||||
--kconfig ${ROOT_KCONFIG}
|
||||
--config ${SDKCONFIG}
|
||||
${defaults_arg}
|
||||
--create-config-if-missing
|
||||
--env "COMPONENT_KCONFIGS=${kconfigs}"
|
||||
--env "COMPONENT_KCONFIGS_PROJBUILD=${kconfigs_projbuild}")
|
||||
|
||||
# Generate the menuconfig target (uses C-based mconf tool, either prebuilt or via mconf target above)
|
||||
add_custom_target(menuconfig
|
||||
${menuconfig_depends}
|
||||
# create any missing config file, with defaults if necessary
|
||||
COMMAND ${confgen_basecommand} --output config ${SDKCONFIG}
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
"COMPONENT_KCONFIGS=${kconfigs}"
|
||||
"COMPONENT_KCONFIGS_PROJBUILD=${kconfigs_projbuild}"
|
||||
"KCONFIG_CONFIG=${SDKCONFIG}"
|
||||
${MCONF} ${ROOT_KCONFIG}
|
||||
VERBATIM
|
||||
USES_TERMINAL)
|
||||
|
||||
# Generate configuration output via confgen.py
|
||||
# makes sdkconfig.h and skdconfig.cmake
|
||||
#
|
||||
# This happens during the cmake run not during the build
|
||||
execute_process(COMMAND ${confgen_basecommand}
|
||||
--output header ${SDKCONFIG_HEADER}
|
||||
--output cmake ${SDKCONFIG_CMAKE}
|
||||
--output json ${SDKCONFIG_JSON}
|
||||
RESULT_VARIABLE config_result)
|
||||
if(config_result)
|
||||
message(FATAL_ERROR "Failed to run confgen.py (${confgen_basecommand}). Error ${config_result}")
|
||||
endif()
|
||||
|
||||
# When sdkconfig file changes in the future, trigger a cmake run
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${SDKCONFIG}")
|
||||
|
||||
# Ditto if either of the generated files are missing/modified (this is a bit irritating as it means
|
||||
# you can't edit these manually without them being regenerated, but I don't know of a better way...)
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${SDKCONFIG_HEADER}")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${SDKCONFIG_CMAKE}")
|
||||
|
||||
# Or if the config generation tool changes
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${IDF_PATH}/tools/kconfig_new/confgen.py")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${IDF_PATH}/tools/kconfig_new/kconfiglib.py")
|
||||
|
||||
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
|
||||
ADDITIONAL_MAKE_CLEAN_FILES
|
||||
"${SDKCONFIG_HEADER}" "${SDKCONFIG_CMAKE}")
|
||||
|
||||
endfunction()
|
140
tools/cmake/project.cmake
Normal file
140
tools/cmake/project.cmake
Normal file
@ -0,0 +1,140 @@
|
||||
# Designed to be included from an IDF app's CMakeLists.txt file
|
||||
#
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# Set IDF_PATH, as nothing else will work without this
|
||||
set(IDF_PATH "$ENV{IDF_PATH}")
|
||||
if(NOT IDF_PATH)
|
||||
# Documentation says you should set IDF_PATH in your environment, but we
|
||||
# can infer it here if it's not set.
|
||||
set(IDF_PATH ${CMAKE_CURRENT_LIST_DIR})
|
||||
endif()
|
||||
file(TO_CMAKE_PATH "${IDF_PATH}" IDF_PATH)
|
||||
set($ENV{IDF_PATH} "${IDF_PATH}")
|
||||
|
||||
#
|
||||
# Load cmake modules
|
||||
#
|
||||
set(CMAKE_MODULE_PATH
|
||||
"${IDF_PATH}/tools/cmake"
|
||||
"${IDF_PATH}/tools/cmake/third_party"
|
||||
${CMAKE_MODULE_PATH})
|
||||
include(GetGitRevisionDescription)
|
||||
include(utilities)
|
||||
include(components)
|
||||
include(kconfig)
|
||||
include(git_submodules)
|
||||
include(idf_functions)
|
||||
|
||||
set_default(PYTHON "python")
|
||||
|
||||
# project
|
||||
#
|
||||
# This macro wraps the cmake 'project' command to add
|
||||
# all of the IDF-specific functionality required
|
||||
#
|
||||
# Implementation Note: This macro wraps 'project' on purpose, because cmake has
|
||||
# some backwards-compatible magic where if you don't call "project" in the
|
||||
# top-level CMakeLists file, it will call it implicitly. However, the implicit
|
||||
# project will not have CMAKE_TOOLCHAIN_FILE set and therefore tries to
|
||||
# create a native build project.
|
||||
#
|
||||
# Therefore, to keep all the IDF "build magic", the cleanest way is to keep the
|
||||
# top-level "project" call but customize it to do what we want in the IDF build.
|
||||
#
|
||||
macro(project name)
|
||||
# Set global variables used by rest of the build
|
||||
idf_set_global_variables()
|
||||
|
||||
# Establish dependencies for components in the build
|
||||
# (this happens before we even generate config...)
|
||||
if(COMPONENTS)
|
||||
# Make sure if an explicit list of COMPONENTS is given, it contains the "common" component requirements
|
||||
# (otherwise, if COMPONENTS is empty then all components will be included in the build.)
|
||||
set(COMPONENTS "${COMPONENTS} ${COMPONENT_REQUIRES_COMMON}")
|
||||
endif()
|
||||
execute_process(COMMAND "${CMAKE_COMMAND}"
|
||||
-D "COMPONENTS=${COMPONENTS}"
|
||||
-D "DEPENDENCIES_FILE=${CMAKE_BINARY_DIR}/component_depends.cmake"
|
||||
-D "COMPONENT_DIRS=${COMPONENT_DIRS}"
|
||||
-D "BOOTLOADER_BUILD=${BOOTLOADER_BUILD}"
|
||||
-P "${IDF_PATH}/tools/cmake/scripts/expand_requirements.cmake"
|
||||
WORKING_DIRECTORY "${IDF_PATH}/tools/cmake")
|
||||
include("${CMAKE_BINARY_DIR}/component_depends.cmake")
|
||||
|
||||
# We now have the following component-related variables:
|
||||
# COMPONENTS is the list of initial components set by the user (or empty to include all components in the build).
|
||||
# BUILD_COMPONENTS is the list of components to include in the build.
|
||||
# BUILD_COMPONENT_PATHS is the paths to all of these components.
|
||||
|
||||
# Print list of components
|
||||
string(REPLACE ";" " " BUILD_COMPONENTS_SPACES "${BUILD_COMPONENTS}")
|
||||
message(STATUS "Component names: ${BUILD_COMPONENTS_SPACES}")
|
||||
unset(BUILD_COMPONENTS_SPACES)
|
||||
message(STATUS "Component paths: ${BUILD_COMPONENT_PATHS}")
|
||||
|
||||
kconfig_set_variables()
|
||||
|
||||
kconfig_process_config()
|
||||
|
||||
# Include sdkconfig.cmake so rest of the build knows the configuration
|
||||
include(${SDKCONFIG_CMAKE})
|
||||
|
||||
# Now the configuration is loaded, set the toolchain appropriately
|
||||
#
|
||||
# TODO: support more toolchains than just ESP32
|
||||
set(CMAKE_TOOLCHAIN_FILE $ENV{IDF_PATH}/tools/cmake/toolchain-esp32.cmake)
|
||||
|
||||
# Declare the actual cmake-level project
|
||||
_project(${name} ASM C CXX)
|
||||
|
||||
# generate compile_commands.json (needs to come after project)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
|
||||
|
||||
# Verify the environment is configured correctly
|
||||
idf_verify_environment()
|
||||
|
||||
# Add some idf-wide definitions
|
||||
idf_set_global_compiler_options()
|
||||
|
||||
# Check git revision (may trigger reruns of cmake)
|
||||
## sets IDF_VER to IDF git revision
|
||||
idf_get_git_revision()
|
||||
## if project uses git, retrieve revision
|
||||
git_describe(PROJECT_VER "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
# Include any top-level project_include.cmake files from components
|
||||
foreach(component ${BUILD_COMPONENT_PATHS})
|
||||
include_if_exists("${component}/project_include.cmake")
|
||||
endforeach()
|
||||
|
||||
#
|
||||
# Add each component to the build as a library
|
||||
#
|
||||
foreach(COMPONENT_PATH ${BUILD_COMPONENT_PATHS})
|
||||
get_filename_component(COMPONENT_NAME ${COMPONENT_PATH} NAME)
|
||||
add_subdirectory(${COMPONENT_PATH} ${COMPONENT_NAME})
|
||||
endforeach()
|
||||
unset(COMPONENT_NAME)
|
||||
unset(COMPONENT_PATH)
|
||||
|
||||
#
|
||||
# Add the app executable to the build (has name of PROJECT.elf)
|
||||
#
|
||||
idf_add_executable()
|
||||
|
||||
# Write project description JSON file
|
||||
make_json_list("${BUILD_COMPONENTS}" build_components_json)
|
||||
make_json_list("${BUILD_COMPONENT_PATHS}" build_component_paths_json)
|
||||
configure_file("${IDF_PATH}/tools/cmake/project_description.json.in"
|
||||
"${CMAKE_BINARY_DIR}/project_description.json")
|
||||
unset(build_components_json)
|
||||
unset(build_component_paths_json)
|
||||
|
||||
#
|
||||
# Finish component registration (add cross-dependencies, make
|
||||
# executable dependent on all components)
|
||||
#
|
||||
components_finish_registration()
|
||||
|
||||
endmacro()
|
18
tools/cmake/project_description.json.in
Normal file
18
tools/cmake/project_description.json.in
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"project_name": "${PROJECT_NAME}",
|
||||
"project_path": "${PROJECT_PATH}",
|
||||
"build_dir": "${CMAKE_BINARY_DIR}",
|
||||
"config_file": "${SDKCONFIG}",
|
||||
"config_defaults": "${SDKCONFIG_DEFAULTS}",
|
||||
"app_elf": "${PROJECT_NAME}.elf",
|
||||
"app_bin": "${PROJECT_NAME}.bin",
|
||||
"git_revision": "${IDF_VER}",
|
||||
"phy_data_partition": "${CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION}",
|
||||
"monitor_baud" : "${CONFIG_MONITOR_BAUD}",
|
||||
"config_environment" : {
|
||||
"COMPONENT_KCONFIGS" : "${COMPONENT_KCONFIGS}",
|
||||
"COMPONENT_KCONFIGS_PROJBUILD" : "${COMPONENT_KCONFIGS_PROJBUILD}"
|
||||
},
|
||||
"build_components" : ${build_components_json},
|
||||
"build_component_paths" : ${build_component_paths_json}
|
||||
}
|
32
tools/cmake/run_cmake_lint.sh
Executable file
32
tools/cmake/run_cmake_lint.sh
Executable file
@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Run cmakelint on all cmake files in IDF_PATH (except third party)
|
||||
#
|
||||
# cmakelint: https://github.com/richq/cmake-lint
|
||||
#
|
||||
# NOTE: This script makes use of features in (currently unreleased)
|
||||
# cmakelint >1.4. Install directly from github as follows:
|
||||
#
|
||||
# pip install https://github.com/richq/cmake-lint/archive/058c6c0ed2536.zip
|
||||
#
|
||||
|
||||
if [ -z "${IDF_PATH}" ]; then
|
||||
echo "IDF_PATH variable needs to be set"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# exclusions include some third-party directories which contain upstream
|
||||
# CMakeLists files
|
||||
find ${IDF_PATH} \
|
||||
-name build -prune \
|
||||
-o -name third_party -prune \
|
||||
\
|
||||
-o -name 'nghttp2' -prune \
|
||||
-o -name 'cJSON' -prune \
|
||||
-o -name 'Findsodium.cmake' -prune \
|
||||
\
|
||||
-o -name CMakeLists.txt -print0 \
|
||||
-o -name '*.cmake' -print0 \
|
||||
| xargs -0 cmakelint --linelength=120 --spaces=4
|
||||
|
||||
|
82
tools/cmake/scripts/data_file_embed_asm.cmake
Normal file
82
tools/cmake/scripts/data_file_embed_asm.cmake
Normal file
@ -0,0 +1,82 @@
|
||||
#
|
||||
# Convert a file (text or binary) into an assembler source file suitable
|
||||
# for gcc. Designed to replicate 'objcopy' with more predictable
|
||||
# naming, and supports appending a null byte for embedding text as
|
||||
# a string.
|
||||
#
|
||||
# Designed to be run as a script with "cmake -P"
|
||||
#
|
||||
# Set variables DATA_FILE, SOURCE_FILE, FILE_TYPE when running this.
|
||||
#
|
||||
# If FILE_TYPE is set to TEXT, a null byte is appended to DATA_FILE's contents
|
||||
# before SOURCE_FILE is created.
|
||||
#
|
||||
# If FILE_TYPE is unset (or any other value), DATA_FILE is copied
|
||||
# verbatim into SOURCE_FILE.
|
||||
#
|
||||
#
|
||||
if(NOT DATA_FILE)
|
||||
message(FATAL_ERROR "DATA_FILE for converting must be specified")
|
||||
endif()
|
||||
|
||||
if(NOT SOURCE_FILE)
|
||||
message(FATAL_ERROR "SOURCE_FILE destination must be specified")
|
||||
endif()
|
||||
|
||||
file(READ "${DATA_FILE}" data HEX)
|
||||
|
||||
string(LENGTH "${data}" data_len)
|
||||
math(EXPR data_len "${data_len} / 2") # 2 hex bytes per byte
|
||||
|
||||
if(FILE_TYPE STREQUAL "TEXT")
|
||||
set(data "${data}00") # null-byte termination
|
||||
endif()
|
||||
|
||||
## Convert string of raw hex bytes to lines of hex bytes as gcc .byte expressions
|
||||
string(REGEX REPLACE "................................" ".byte \\0\n" data "${data}") # 16 bytes per line
|
||||
string(REGEX REPLACE "[^\n]+$" ".byte \\0\n" data "${data}") # last line
|
||||
string(REGEX REPLACE "[0-9a-f][0-9a-f]" "0x\\0, " data "${data}") # hex formatted C bytes
|
||||
string(REGEX REPLACE ", \n" "\n" data "${data}") # trim the last comma
|
||||
|
||||
## Come up with C-friendly symbol name based on source file
|
||||
get_filename_component(source_filename "${DATA_FILE}" NAME)
|
||||
string(MAKE_C_IDENTIFIER "${source_filename}" varname)
|
||||
|
||||
function(append str)
|
||||
file(APPEND "${SOURCE_FILE}" "${str}")
|
||||
endfunction()
|
||||
|
||||
function(append_line str)
|
||||
append("${str}\n")
|
||||
endfunction()
|
||||
|
||||
function(append_identifier symbol)
|
||||
append_line("\n.global ${symbol}")
|
||||
append("${symbol}:")
|
||||
if(${ARGC} GREATER 1) # optional comment
|
||||
append(" /* ${ARGV1} */")
|
||||
endif()
|
||||
append("\n")
|
||||
endfunction()
|
||||
|
||||
file(WRITE "${SOURCE_FILE}" "/*")
|
||||
append_line(" * Data converted from ${DATA_FILE}")
|
||||
if(FILE_TYPE STREQUAL "TEXT")
|
||||
append_line(" * (null byte appended)")
|
||||
endif()
|
||||
append_line(" */")
|
||||
|
||||
append_line(".data")
|
||||
append_identifier("${varname}")
|
||||
append_identifier("_binary_${varname}_start" "for objcopy compatibility")
|
||||
append("${data}")
|
||||
|
||||
append_identifier("_binary_${varname}_end" "for objcopy compatibility")
|
||||
|
||||
append_line("")
|
||||
if(FILE_TYPE STREQUAL "TEXT")
|
||||
append_identifier("${varname}_length" "not including null byte")
|
||||
else()
|
||||
append_identifier("${varname}_length")
|
||||
endif()
|
||||
append_line(".word ${data_len}")
|
216
tools/cmake/scripts/expand_requirements.cmake
Normal file
216
tools/cmake/scripts/expand_requirements.cmake
Normal file
@ -0,0 +1,216 @@
|
||||
# expand_requires.cmake is a utility cmake script to expand component requirements early in the build,
|
||||
# before the components are ready to be included.
|
||||
#
|
||||
# Parameters:
|
||||
# - COMPONENTS = Space-separated list of initial components to include in the build.
|
||||
# Can be empty, in which case all components are in the build.
|
||||
# - DEPENDENCIES_FILE = Path of generated cmake file which will contain the expanded dependencies for these
|
||||
# components.
|
||||
# - COMPONENT_DIRS = List of paths to search for all components.
|
||||
# - DEBUG = Set -DDEBUG=1 to debug component lists in the build.
|
||||
#
|
||||
# If successful, DEPENDENCIES_FILE can be expanded to set BUILD_COMPONENTS & BUILD_COMPONENT_PATHS with all
|
||||
# components required for the build, and the get_component_requirements() function to return each component's
|
||||
# recursively expanded requirements.
|
||||
#
|
||||
# TODO: Error out if a component requirement is missing
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
include("utilities.cmake")
|
||||
|
||||
if(NOT DEPENDENCIES_FILE)
|
||||
message(FATAL_ERROR "DEPENDENCIES_FILE must be set.")
|
||||
endif()
|
||||
|
||||
if(NOT COMPONENT_DIRS)
|
||||
message(FATAL_ERROR "COMPONENT_DIRS variable must be set")
|
||||
endif()
|
||||
spaces2list(COMPONENT_DIRS)
|
||||
|
||||
function(debug message)
|
||||
if(DEBUG)
|
||||
message(STATUS "${message}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Dummy register_component used to save requirements variables as global properties, for later expansion
|
||||
#
|
||||
# (expand_component_requirements() includes the component CMakeLists.txt, which then sets its component variables,
|
||||
# calls this dummy macro, and immediately exits again.)
|
||||
macro(register_component)
|
||||
spaces2list(COMPONENT_REQUIRES)
|
||||
set_property(GLOBAL PROPERTY "${COMPONENT}_REQUIRES" "${COMPONENT_REQUIRES}")
|
||||
spaces2list(COMPONENT_PRIV_REQUIRES)
|
||||
set_property(GLOBAL PROPERTY "${COMPONENT}_PRIV_REQUIRES" "${COMPONENT_PRIV_REQUIRES}")
|
||||
|
||||
# This is tricky: we override register_component() so it returns out of the component CMakeLists.txt
|
||||
# (as we're declaring it as a macro not a function, so it doesn't have its own scope.)
|
||||
#
|
||||
# This means no targets are defined, and the component expansion ends early.
|
||||
return()
|
||||
endmacro()
|
||||
|
||||
macro(register_config_only_component)
|
||||
register_component()
|
||||
endmacro()
|
||||
|
||||
# Given a component name (find_name) and a list of component paths (component_paths),
|
||||
# return the path to the component in 'variable'
|
||||
#
|
||||
# Fatal error is printed if the component is not found.
|
||||
function(find_component_path find_name component_paths variable)
|
||||
foreach(path ${component_paths})
|
||||
get_filename_component(name "${path}" NAME)
|
||||
if("${name}" STREQUAL "${find_name}")
|
||||
set("${variable}" "${path}" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
endforeach()
|
||||
# TODO: find a way to print the dependency chain that lead to this not-found component
|
||||
message(WARNING "Required component ${find_name} is not found in any of the provided COMPONENT_DIRS")
|
||||
endfunction()
|
||||
|
||||
# components_find_all: Search 'component_dirs' for components and return them
|
||||
# as a list of names in 'component_names' and a list of full paths in
|
||||
# 'component_paths'
|
||||
#
|
||||
# component_paths contains only unique component names. Directories
|
||||
# earlier in the component_dirs list take precedence.
|
||||
function(components_find_all component_dirs component_paths component_names)
|
||||
# component_dirs entries can be files or lists of files
|
||||
set(paths "")
|
||||
set(names "")
|
||||
|
||||
# start by expanding the component_dirs list with all subdirectories
|
||||
foreach(dir ${component_dirs})
|
||||
# Iterate any subdirectories for values
|
||||
file(GLOB subdirs LIST_DIRECTORIES true "${dir}/*")
|
||||
foreach(subdir ${subdirs})
|
||||
set(component_dirs "${component_dirs};${subdir}")
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
# Look for a component in each component_dirs entry
|
||||
foreach(dir ${component_dirs})
|
||||
file(GLOB component "${dir}/CMakeLists.txt")
|
||||
if(component)
|
||||
get_filename_component(component "${component}" DIRECTORY)
|
||||
get_filename_component(name "${component}" NAME)
|
||||
if(NOT name IN_LIST names)
|
||||
set(names "${names};${name}")
|
||||
set(paths "${paths};${component}")
|
||||
endif()
|
||||
|
||||
else() # no CMakeLists.txt file
|
||||
# test for legacy component.mk and warn
|
||||
file(GLOB legacy_component "${dir}/component.mk")
|
||||
if(legacy_component)
|
||||
get_filename_component(legacy_component "${legacy_component}" DIRECTORY)
|
||||
message(WARNING "Component ${legacy_component} contains old-style component.mk but no CMakeLists.txt. "
|
||||
"Component will be skipped.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endforeach()
|
||||
|
||||
set(${component_paths} ${paths} PARENT_SCOPE)
|
||||
set(${component_names} ${names} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# expand_component_requirements: Recursively expand a component's requirements,
|
||||
# setting global properties BUILD_COMPONENTS & BUILD_COMPONENT_PATHS and
|
||||
# also invoking the components to call register_component() above,
|
||||
# which will add per-component global properties with dependencies, etc.
|
||||
function(expand_component_requirements component)
|
||||
get_property(build_components GLOBAL PROPERTY BUILD_COMPONENTS)
|
||||
if(${component} IN_LIST build_components)
|
||||
return() # already added this component
|
||||
endif()
|
||||
|
||||
find_component_path("${component}" "${ALL_COMPONENT_PATHS}" component_path)
|
||||
debug("Expanding dependencies of ${component} @ ${component_path}")
|
||||
if(NOT component_path)
|
||||
set_property(GLOBAL APPEND PROPERTY COMPONENTS_NOT_FOUND ${component})
|
||||
return()
|
||||
endif()
|
||||
|
||||
# include the component CMakeLists.txt to expand its properties
|
||||
# into the global cache (via register_component(), above)
|
||||
unset(COMPONENT_REQUIRES)
|
||||
unset(COMPONENT_PRIV_REQUIRES)
|
||||
set(COMPONENT ${component})
|
||||
include(${component_path}/CMakeLists.txt)
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY BUILD_COMPONENT_PATHS ${component_path})
|
||||
set_property(GLOBAL APPEND PROPERTY BUILD_COMPONENTS ${component})
|
||||
|
||||
get_property(requires GLOBAL PROPERTY "${component}_REQUIRES")
|
||||
get_property(requires_priv GLOBAL PROPERTY "${component}_PRIV_REQUIRES")
|
||||
foreach(req ${requires} ${requires_priv})
|
||||
expand_component_requirements(${req})
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
|
||||
# Main functionality goes here
|
||||
|
||||
# Find every available component in COMPONENT_DIRS, save as ALL_COMPONENT_PATHS and ALL_COMPONENTS
|
||||
components_find_all("${COMPONENT_DIRS}" ALL_COMPONENT_PATHS ALL_COMPONENTS)
|
||||
|
||||
if(NOT COMPONENTS)
|
||||
set(COMPONENTS "${ALL_COMPONENTS}")
|
||||
endif()
|
||||
spaces2list(COMPONENTS)
|
||||
|
||||
debug("ALL_COMPONENT_PATHS ${ALL_COMPONENT_PATHS}")
|
||||
debug("ALL_COMPONENTS ${ALL_COMPONENTS}")
|
||||
|
||||
set_property(GLOBAL PROPERTY BUILD_COMPONENTS "")
|
||||
set_property(GLOBAL PROPERTY BUILD_COMPONENT_PATHS "")
|
||||
set_property(GLOBAL PROPERTY COMPONENTS_NOT_FOUND "")
|
||||
|
||||
foreach(component ${COMPONENTS})
|
||||
debug("Expanding initial component ${component}")
|
||||
expand_component_requirements(${component})
|
||||
endforeach()
|
||||
|
||||
get_property(build_components GLOBAL PROPERTY BUILD_COMPONENTS)
|
||||
get_property(build_component_paths GLOBAL PROPERTY BUILD_COMPONENT_PATHS)
|
||||
get_property(not_found GLOBAL PROPERTY COMPONENTS_NOT_FOUND)
|
||||
|
||||
debug("components in build: ${build_components}")
|
||||
debug("components in build: ${build_component_paths}")
|
||||
debug("components not found: ${not_found}")
|
||||
|
||||
function(line contents)
|
||||
file(APPEND "${DEPENDENCIES_FILE}" "${contents}\n")
|
||||
endfunction()
|
||||
|
||||
file(WRITE "${DEPENDENCIES_FILE}" "# Component requirements generated by expand_requirements.cmake\n\n")
|
||||
line("set(BUILD_COMPONENTS ${build_components})")
|
||||
line("set(BUILD_COMPONENT_PATHS ${build_component_paths})")
|
||||
line("")
|
||||
|
||||
line("# get_component_requirements: Generated function to read the dependencies of a given component.")
|
||||
line("#")
|
||||
line("# Parameters:")
|
||||
line("# - component: Name of component")
|
||||
line("# - var_requires: output variable name. Set to recursively expanded COMPONENT_REQUIRES ")
|
||||
line("# for this component.")
|
||||
line("# - var_private_requires: output variable name. Set to recursively expanded COMPONENT_PRIV_REQUIRES ")
|
||||
line("# for this component.")
|
||||
line("#")
|
||||
line("# Throws a fatal error if 'componeont' is not found (indicates a build system problem).")
|
||||
line("#")
|
||||
line("function(get_component_requirements component var_requires var_private_requires)")
|
||||
foreach(build_component ${build_components})
|
||||
get_property(reqs GLOBAL PROPERTY "${build_component}_REQUIRES")
|
||||
get_property(private_reqs GLOBAL PROPERTY "${build_component}_PRIV_REQUIRES")
|
||||
line(" if(\"\$\{component}\" STREQUAL \"${build_component}\")")
|
||||
line(" set(\${var_requires} \"${reqs}\" PARENT_SCOPE)")
|
||||
line(" set(\${var_private_requires} \"${private_reqs}\" PARENT_SCOPE)")
|
||||
line(" return()")
|
||||
line(" endif()")
|
||||
endforeach()
|
||||
|
||||
line(" message(FATAL_ERROR \"Component not found: \${component}\")")
|
||||
line("endfunction()")
|
133
tools/cmake/third_party/GetGitRevisionDescription.cmake
vendored
Normal file
133
tools/cmake/third_party/GetGitRevisionDescription.cmake
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
# - Returns a version string from Git
|
||||
#
|
||||
# These functions force a re-configure on each git commit so that you can
|
||||
# trust the values of the variables in your build system.
|
||||
#
|
||||
# get_git_head_revision(<refspecvar> <hashvar> <repo dir> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the refspec and sha hash of the current head revision
|
||||
#
|
||||
# git_describe(<var> <repo dir> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the results of git describe on the source tree, and adjusting
|
||||
# the output so that it tests false if an error occurs.
|
||||
#
|
||||
# git_get_exact_tag(<var> <repo dir> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the results of git describe --exact-match on the source tree,
|
||||
# and adjusting the output so that it tests false if there was no exact
|
||||
# matching tag.
|
||||
#
|
||||
# Requires CMake 2.6 or newer (uses the 'function' command)
|
||||
#
|
||||
# Original Author:
|
||||
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
|
||||
# http://academic.cleardefinition.com
|
||||
# Iowa State University HCI Graduate Program/VRAC
|
||||
#
|
||||
# Copyright Iowa State University 2009-2010.
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
# Updated 2018 Espressif Systems to add _repo_dir argument
|
||||
# to get revision of other repositories
|
||||
|
||||
if(__get_git_revision_description)
|
||||
return()
|
||||
endif()
|
||||
set(__get_git_revision_description YES)
|
||||
|
||||
# We must run the following at "include" time, not at function call time,
|
||||
# to find the path to this module rather than the path to a calling list file
|
||||
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
|
||||
|
||||
function(get_git_head_revision _refspecvar _hashvar _repo_dir)
|
||||
set(GIT_PARENT_DIR "${_repo_dir}")
|
||||
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
|
||||
while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories
|
||||
set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
|
||||
get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
|
||||
if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
|
||||
# We have reached the root directory, we are not in git
|
||||
set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
|
||||
set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
|
||||
endwhile()
|
||||
# check if this is a submodule
|
||||
if(NOT IS_DIRECTORY ${GIT_DIR})
|
||||
file(READ ${GIT_DIR} submodule)
|
||||
string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
|
||||
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
|
||||
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
|
||||
endif()
|
||||
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
|
||||
if(NOT EXISTS "${GIT_DATA}")
|
||||
file(MAKE_DIRECTORY "${GIT_DATA}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${GIT_DIR}/HEAD")
|
||||
return()
|
||||
endif()
|
||||
set(HEAD_FILE "${GIT_DATA}/HEAD")
|
||||
configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
|
||||
|
||||
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
|
||||
"${GIT_DATA}/grabRef.cmake"
|
||||
@ONLY)
|
||||
include("${GIT_DATA}/grabRef.cmake")
|
||||
|
||||
set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
|
||||
set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_describe _var _repo_dir)
|
||||
if(NOT GIT_FOUND)
|
||||
find_package(Git QUIET)
|
||||
endif()
|
||||
get_git_head_revision(refspec hash "${_repo_dir}")
|
||||
if(NOT GIT_FOUND)
|
||||
set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
if(NOT hash)
|
||||
set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# TODO sanitize
|
||||
#if((${ARGN}" MATCHES "&&") OR
|
||||
# (ARGN MATCHES "||") OR
|
||||
# (ARGN MATCHES "\\;"))
|
||||
# message("Please report the following error to the project!")
|
||||
# message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
|
||||
#endif()
|
||||
|
||||
#message(STATUS "Arguments to execute_process: ${ARGN}")
|
||||
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}"
|
||||
describe
|
||||
${hash}
|
||||
${ARGN}
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE
|
||||
res
|
||||
OUTPUT_VARIABLE
|
||||
out
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if(NOT res EQUAL 0)
|
||||
set(out "${out}-${res}-NOTFOUND")
|
||||
endif()
|
||||
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_get_exact_tag _var _repo_dir)
|
||||
git_describe(out "${_repo_dir}" --exact-match ${ARGN})
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
41
tools/cmake/third_party/GetGitRevisionDescription.cmake.in
vendored
Normal file
41
tools/cmake/third_party/GetGitRevisionDescription.cmake.in
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
#
|
||||
# Internal file for GetGitRevisionDescription.cmake
|
||||
#
|
||||
# Requires CMake 2.6 or newer (uses the 'function' command)
|
||||
#
|
||||
# Original Author:
|
||||
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
|
||||
# http://academic.cleardefinition.com
|
||||
# Iowa State University HCI Graduate Program/VRAC
|
||||
#
|
||||
# Copyright Iowa State University 2009-2010.
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
set(HEAD_HASH)
|
||||
|
||||
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
|
||||
|
||||
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
|
||||
if(HEAD_CONTENTS MATCHES "ref")
|
||||
# named branch
|
||||
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
|
||||
if(EXISTS "@GIT_DIR@/${HEAD_REF}")
|
||||
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
else()
|
||||
configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY)
|
||||
file(READ "@GIT_DATA@/packed-refs" PACKED_REFS)
|
||||
if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}")
|
||||
set(HEAD_HASH "${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
# detached HEAD
|
||||
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
endif()
|
||||
|
||||
if(NOT HEAD_HASH)
|
||||
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
|
||||
string(STRIP "${HEAD_HASH}" HEAD_HASH)
|
||||
endif()
|
7
tools/cmake/toolchain-esp32.cmake
Normal file
7
tools/cmake/toolchain-esp32.cmake
Normal file
@ -0,0 +1,7 @@
|
||||
set(CMAKE_SYSTEM_NAME Generic)
|
||||
|
||||
set(CMAKE_C_COMPILER xtensa-esp32-elf-gcc)
|
||||
set(CMAKE_CXX_COMPILER xtensa-esp32-elf-g++)
|
||||
set(CMAKE_ASM_COMPILER xtensa-esp32-elf-gcc)
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-nostdlib" CACHE STRING "Linker Base Flags")
|
181
tools/cmake/utilities.cmake
Normal file
181
tools/cmake/utilities.cmake
Normal file
@ -0,0 +1,181 @@
|
||||
# set_default
|
||||
#
|
||||
# Define a variable to a default value if otherwise unset.
|
||||
#
|
||||
# Priority for new value is:
|
||||
# - Existing cmake value (ie set with cmake -D, or already set in CMakeLists)
|
||||
# - Value of any non-empty environment variable of the same name
|
||||
# - Default value as provided to function
|
||||
#
|
||||
function(set_default variable default_value)
|
||||
if(NOT ${variable})
|
||||
if($ENV{${variable}})
|
||||
set(${variable} $ENV{${variable}} PARENT_SCOPE)
|
||||
else()
|
||||
set(${variable} ${default_value} PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# spaces2list
|
||||
#
|
||||
# Take a variable whose value was space-delimited values, convert to a cmake
|
||||
# list (semicolon-delimited)
|
||||
#
|
||||
# Note: if using this for directories, keeps the issue in place that
|
||||
# directories can't contain spaces...
|
||||
#
|
||||
# TODO: look at cmake separate_arguments, which is quote-aware
|
||||
function(spaces2list variable_name)
|
||||
string(REPLACE " " ";" tmp "${${variable_name}}")
|
||||
set("${variable_name}" "${tmp}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
|
||||
# lines2list
|
||||
#
|
||||
# Take a variable with multiple lines of output in it, convert it
|
||||
# to a cmake list (semicolon-delimited), one line per item
|
||||
#
|
||||
function(lines2list variable_name)
|
||||
string(REGEX REPLACE "\r?\n" ";" tmp "${${variable_name}}")
|
||||
string(REGEX REPLACE ";;" ";" tmp "${tmp}")
|
||||
set("${variable_name}" "${tmp}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
|
||||
# move_if_different
|
||||
#
|
||||
# If 'source' has different md5sum to 'destination' (or destination
|
||||
# does not exist, move it across.
|
||||
#
|
||||
# If 'source' has the same md5sum as 'destination', delete 'source'.
|
||||
#
|
||||
# Avoids timestamp updates for re-generated files where content hasn't
|
||||
# changed.
|
||||
function(move_if_different source destination)
|
||||
set(do_copy 1)
|
||||
file(GLOB dest_exists ${destination})
|
||||
if(dest_exists)
|
||||
file(MD5 ${source} source_md5)
|
||||
file(MD5 ${destination} dest_md5)
|
||||
if(source_md5 STREQUAL dest_md5)
|
||||
set(do_copy "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(do_copy)
|
||||
message("Moving ${source} -> ${destination}")
|
||||
file(RENAME ${source} ${destination})
|
||||
else()
|
||||
message("Not moving ${source} -> ${destination}")
|
||||
file(REMOVE ${source})
|
||||
endif()
|
||||
|
||||
endfunction()
|
||||
|
||||
|
||||
# add_compile_options variant for C++ code only
|
||||
#
|
||||
# This adds global options, set target properties for
|
||||
# component-specific flags
|
||||
function(add_cxx_compile_options)
|
||||
foreach(option ${ARGV})
|
||||
# note: the Visual Studio Generator doesn't support this...
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:${option}>)
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# add_compile_options variant for C code only
|
||||
#
|
||||
# This adds global options, set target properties for
|
||||
# component-specific flags
|
||||
function(add_c_compile_options)
|
||||
foreach(option ${ARGV})
|
||||
# note: the Visual Studio Generator doesn't support this...
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C>:${option}>)
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
|
||||
# target_add_binary_data adds binary data into the built target,
|
||||
# by converting it to a generated source file which is then compiled
|
||||
# to a binary object as part of the build
|
||||
function(target_add_binary_data target embed_file embed_type)
|
||||
|
||||
get_filename_component(embed_file "${embed_file}" ABSOLUTE)
|
||||
|
||||
get_filename_component(name "${embed_file}" NAME)
|
||||
set(embed_srcfile "${CMAKE_BINARY_DIR}/${name}.S")
|
||||
|
||||
add_custom_command(OUTPUT "${embed_srcfile}"
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-D "DATA_FILE=${embed_file}"
|
||||
-D "SOURCE_FILE=${embed_srcfile}"
|
||||
-D "FILE_TYPE=${embed_type}"
|
||||
-P "${IDF_PATH}/tools/cmake/scripts/data_file_embed_asm.cmake"
|
||||
MAIN_DEPENDENCY "${embed_file}"
|
||||
DEPENDENCIES "${IDF_PATH}/tools/cmake/scripts/data_file_embed_asm.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
|
||||
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${embed_srcfile}")
|
||||
|
||||
target_sources("${target}" PRIVATE "${embed_srcfile}")
|
||||
endfunction()
|
||||
|
||||
macro(include_if_exists path)
|
||||
if(EXISTS "${path}")
|
||||
include("${path}")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# Append a single line to the file specified
|
||||
# The line ending is determined by the host OS
|
||||
function(file_append_line file line)
|
||||
if(ENV{MSYSTEM} OR CMAKE_HOST_WIN32)
|
||||
set(line_ending "\r\n")
|
||||
else() # unix
|
||||
set(line_ending "\n")
|
||||
endif()
|
||||
file(READ ${file} existing)
|
||||
string(FIND ${existing} ${line_ending} last_newline REVERSE)
|
||||
string(LENGTH ${existing} length)
|
||||
math(EXPR length "${length}-1")
|
||||
if(NOT length EQUAL last_newline) # file doesn't end with a newline
|
||||
file(APPEND "${file}" "${line_ending}")
|
||||
endif()
|
||||
file(APPEND "${file}" "${line}${line_ending}")
|
||||
endfunction()
|
||||
|
||||
# Add one or more linker scripts to the target, including a link-time dependency
|
||||
#
|
||||
# Automatically adds a -L search path for the containing directory (if found),
|
||||
# and then adds -T with the filename only. This allows INCLUDE directives to be
|
||||
# used to include other linker scripts in the same directory.
|
||||
function(target_linker_script target)
|
||||
foreach(scriptfile ${ARGN})
|
||||
get_filename_component(abs_script "${scriptfile}" ABSOLUTE)
|
||||
message(STATUS "Adding linker script ${abs_script}")
|
||||
|
||||
get_filename_component(search_dir "${abs_script}" DIRECTORY)
|
||||
get_filename_component(scriptname "${abs_script}" NAME)
|
||||
|
||||
get_target_property(link_libraries "${target}" LINK_LIBRARIES)
|
||||
list(FIND "${link_libraries}" "-L ${search_dir}" found_search_dir)
|
||||
if(found_search_dir EQUAL "-1") # not already added as a search path
|
||||
target_link_libraries("${target}" "-L ${search_dir}")
|
||||
endif()
|
||||
|
||||
target_link_libraries("${target}" "-T ${scriptname}")
|
||||
|
||||
# Note: In ESP-IDF, most targets are libraries and libary LINK_DEPENDS don't propagate to
|
||||
# executable(s) the library is linked to. This is done manually in components.cmake.
|
||||
set_property(TARGET "${target}" APPEND PROPERTY LINK_DEPENDS "${abs_script}")
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# Convert a CMake list to a JSON list and store it in a variable
|
||||
function(make_json_list list variable)
|
||||
string(REPLACE ";" "\", \"" result "[ \"${list}\" ]")
|
||||
set("${variable}" "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
389
tools/idf.py
Executable file
389
tools/idf.py
Executable file
@ -0,0 +1,389 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 'idf.py' is a top-level config/build command line tool for ESP-IDF
|
||||
#
|
||||
# You don't have to use idf.py, you can use cmake directly
|
||||
# (or use cmake in an IDE)
|
||||
#
|
||||
#
|
||||
#
|
||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import sys
|
||||
import argparse
|
||||
import os
|
||||
import os.path
|
||||
import subprocess
|
||||
import multiprocessing
|
||||
import re
|
||||
import shutil
|
||||
import json
|
||||
|
||||
class FatalError(RuntimeError):
|
||||
"""
|
||||
Wrapper class for runtime errors that aren't caused by bugs in idf.py or the build proces.s
|
||||
"""
|
||||
pass
|
||||
|
||||
# Use this Python interpreter for any subprocesses we launch
|
||||
PYTHON=sys.executable
|
||||
|
||||
# note: os.environ changes don't automatically propagate to child processes,
|
||||
# you have to pass this in explicitly
|
||||
os.environ["PYTHON"]=sys.executable
|
||||
|
||||
# Make flavors, across the various kinds of Windows environments & POSIX...
|
||||
if "MSYSTEM" in os.environ: # MSYS
|
||||
MAKE_CMD = "make"
|
||||
MAKE_GENERATOR = "MSYS Makefiles"
|
||||
elif os.name == 'nt': # other Windows
|
||||
MAKE_CMD = "mingw32-make"
|
||||
MAKE_GENERATOR = "MinGW Makefiles"
|
||||
else:
|
||||
MAKE_CMD = "make"
|
||||
MAKE_GENERATOR = "Unix Makefiles"
|
||||
|
||||
GENERATORS = [
|
||||
# ('generator name', 'build command line', 'version command line', 'verbose flag')
|
||||
("Ninja", [ "ninja" ], [ "ninja", "--version" ], "-v"),
|
||||
(MAKE_GENERATOR, [ MAKE_CMD, "-j", str(multiprocessing.cpu_count()+2) ], [ "make", "--version" ], "VERBOSE=1"),
|
||||
]
|
||||
GENERATOR_CMDS = dict( (a[0], a[1]) for a in GENERATORS )
|
||||
GENERATOR_VERBOSE = dict( (a[0], a[3]) for a in GENERATORS )
|
||||
|
||||
def _run_tool(tool_name, args, cwd):
|
||||
def quote_arg(arg):
|
||||
" Quote 'arg' if necessary "
|
||||
if " " in arg and not (arg.startswith('"') or arg.startswith("'")):
|
||||
return "'" + arg + "'"
|
||||
return arg
|
||||
display_args = " ".join(quote_arg(arg) for arg in args)
|
||||
print("Running %s in directory %s" % (tool_name, quote_arg(cwd)))
|
||||
print('Executing "%s"...' % display_args)
|
||||
try:
|
||||
# Note: we explicitly pass in os.environ here, as we may have set IDF_PATH there during startup
|
||||
subprocess.check_call(args, env=os.environ, cwd=cwd)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise FatalError("%s failed with exit code %d" % (tool_name, e.returncode))
|
||||
|
||||
|
||||
def check_environment():
|
||||
"""
|
||||
Verify the environment contains the top-level tools we need to operate
|
||||
|
||||
(cmake will check a lot of other things)
|
||||
"""
|
||||
if not executable_exists(["cmake", "--version"]):
|
||||
raise FatalError("'cmake' must be available on the PATH to use idf.py")
|
||||
# find the directory idf.py is in, then the parent directory of this, and assume this is IDF_PATH
|
||||
detected_idf_path = os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if "IDF_PATH" in os.environ:
|
||||
set_idf_path = os.path.realpath(os.environ["IDF_PATH"])
|
||||
if set_idf_path != detected_idf_path:
|
||||
print("WARNING: IDF_PATH environment variable is set to %s but idf.py path indicates IDF directory %s. Using the environment variable directory, but results may be unexpected..."
|
||||
% (set_idf_path, detected_idf_path))
|
||||
else:
|
||||
os.environ["IDF_PATH"] = detected_idf_path
|
||||
|
||||
def executable_exists(args):
|
||||
try:
|
||||
subprocess.check_output(args)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def detect_cmake_generator():
|
||||
"""
|
||||
Find the default cmake generator, if none was specified. Raises an exception if no valid generator is found.
|
||||
"""
|
||||
for (generator, _, version_check, _) in GENERATORS:
|
||||
if executable_exists(version_check):
|
||||
return generator
|
||||
raise FatalError("To use idf.py, either the 'ninja' or 'GNU make' build tool must be available in the PATH")
|
||||
|
||||
def _ensure_build_directory(args, always_run_cmake=False):
|
||||
"""Check the build directory exists and that cmake has been run there.
|
||||
|
||||
If this isn't the case, create the build directory (if necessary) and
|
||||
do an initial cmake run to configure it.
|
||||
|
||||
This function will also check args.generator parameter. If the parameter is incompatible with
|
||||
the build directory, an error is raised. If the parameter is None, this function will set it to
|
||||
an auto-detected default generator or to the value already configured in the build directory.
|
||||
"""
|
||||
project_dir = args.project_dir
|
||||
# Verify the project directory
|
||||
if not os.path.isdir(project_dir):
|
||||
if not os.path.exists(project_dir):
|
||||
raise FatalError("Project directory %s does not exist")
|
||||
else:
|
||||
raise FatalError("%s must be a project directory")
|
||||
if not os.path.exists(os.path.join(project_dir, "CMakeLists.txt")):
|
||||
raise FatalError("CMakeLists.txt not found in project directory %s" % project_dir)
|
||||
|
||||
# Verify/create the build directory
|
||||
build_dir = args.build_dir
|
||||
if not os.path.isdir(build_dir):
|
||||
os.mkdir(build_dir)
|
||||
cache_path = os.path.join(build_dir, "CMakeCache.txt")
|
||||
if not os.path.exists(cache_path) or always_run_cmake:
|
||||
if args.generator is None:
|
||||
args.generator = detect_cmake_generator()
|
||||
try:
|
||||
cmake_args = ["cmake", "-G", args.generator]
|
||||
if not args.no_warnings:
|
||||
cmake_args += [ "--warn-uninitialized" ]
|
||||
if args.no_ccache:
|
||||
cmake_args += [ "-DCCACHE_DISABLE=1" ]
|
||||
cmake_args += [ project_dir]
|
||||
_run_tool("cmake", cmake_args, cwd=args.build_dir)
|
||||
except:
|
||||
# don't allow partially valid CMakeCache.txt files,
|
||||
# to keep the "should I run cmake?" logic simple
|
||||
if os.path.exists(cache_path):
|
||||
os.remove(cache_path)
|
||||
raise
|
||||
|
||||
# Learn some things from the CMakeCache.txt file in the build directory
|
||||
cache = parse_cmakecache(cache_path)
|
||||
try:
|
||||
generator = cache["CMAKE_GENERATOR"]
|
||||
except KeyError:
|
||||
generator = detect_cmake_generator()
|
||||
if args.generator is None:
|
||||
args.generator = generator # reuse the previously configured generator, if none was given
|
||||
if generator != args.generator:
|
||||
raise FatalError("Build is configured for generator '%s' not '%s'. Run 'idf.py fullclean' to start again."
|
||||
% (generator, args.generator))
|
||||
|
||||
try:
|
||||
home_dir = cache["CMAKE_HOME_DIRECTORY"]
|
||||
if os.path.normcase(os.path.realpath(home_dir)) != os.path.normcase(os.path.realpath(project_dir)):
|
||||
raise FatalError("Build directory '%s' configured for project '%s' not '%s'. Run 'idf.py fullclean' to start again."
|
||||
% (build_dir, os.path.realpath(home_dir), os.path.realpath(project_dir)))
|
||||
except KeyError:
|
||||
pass # if cmake failed part way, CMAKE_HOME_DIRECTORY may not be set yet
|
||||
|
||||
|
||||
def parse_cmakecache(path):
|
||||
"""
|
||||
Parse the CMakeCache file at 'path'.
|
||||
|
||||
Returns a dict of name:value.
|
||||
|
||||
CMakeCache entries also each have a "type", but this is currently ignored.
|
||||
"""
|
||||
result = {}
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
# cmake cache lines look like: CMAKE_CXX_FLAGS_DEBUG:STRING=-g
|
||||
# groups are name, type, value
|
||||
m = re.match(r"^([^#/:=]+):([^:=]+)=(.+)\n$", line)
|
||||
if m:
|
||||
result[m.group(1)] = m.group(3)
|
||||
return result
|
||||
|
||||
def build_target(target_name, args):
|
||||
"""
|
||||
Execute the target build system to build target 'target_name'
|
||||
|
||||
Calls _ensure_build_directory() which will run cmake to generate a build
|
||||
directory (with the specified generator) as needed.
|
||||
"""
|
||||
_ensure_build_directory(args)
|
||||
generator_cmd = GENERATOR_CMDS[args.generator]
|
||||
if not args.no_ccache:
|
||||
# Setting CCACHE_BASEDIR & CCACHE_NO_HASHDIR ensures that project paths aren't stored in the ccache entries
|
||||
# (this means ccache hits can be shared between different projects. It may mean that some debug information
|
||||
# will point to files in another project, if these files are perfect duplicates of each other.)
|
||||
#
|
||||
# It would be nicer to set these from cmake, but there's no cross-platform way to set build-time environment
|
||||
#os.environ["CCACHE_BASEDIR"] = args.build_dir
|
||||
#os.environ["CCACHE_NO_HASHDIR"] = "1"
|
||||
pass
|
||||
if args.verbose:
|
||||
generator_cmd += [ GENERATOR_VERBOSE[args.generator] ]
|
||||
|
||||
_run_tool(generator_cmd[0], generator_cmd + [target_name], args.build_dir)
|
||||
|
||||
|
||||
def _get_esptool_args(args):
|
||||
esptool_path = os.path.join(os.environ["IDF_PATH"], "components/esptool_py/esptool/esptool.py")
|
||||
result = [ PYTHON, esptool_path ]
|
||||
if args.port is not None:
|
||||
result += [ "-p", args.port ]
|
||||
result += [ "-b", str(args.baud) ]
|
||||
return result
|
||||
|
||||
def flash(action, args):
|
||||
"""
|
||||
Run esptool to flash the entire project, from an argfile generated by the build system
|
||||
"""
|
||||
flasher_args_path = { # action -> name of flasher args file generated by build system
|
||||
"bootloader-flash": "flash_bootloader_args",
|
||||
"partition_table-flash": "flash_partition_table_args",
|
||||
"app-flash": "flash_app_args",
|
||||
"flash": "flash_project_args",
|
||||
}[action]
|
||||
esptool_args = _get_esptool_args(args)
|
||||
esptool_args += [ "write_flash", "@"+flasher_args_path ]
|
||||
_run_tool("esptool.py", esptool_args, args.build_dir)
|
||||
|
||||
|
||||
def erase_flash(action, args):
|
||||
esptool_args = _get_esptool_args(args)
|
||||
esptool_args += [ "erase_flash" ]
|
||||
_run_tool("esptool.py", esptool_args, args.build_dir)
|
||||
|
||||
|
||||
def monitor(action, args):
|
||||
"""
|
||||
Run idf_monitor.py to watch build output
|
||||
"""
|
||||
desc_path = os.path.join(args.build_dir, "project_description.json")
|
||||
if not os.path.exists(desc_path):
|
||||
_ensure_build_directory(args)
|
||||
with open(desc_path, "r") as f:
|
||||
project_desc = json.load(f)
|
||||
|
||||
elf_file = os.path.join(args.build_dir, project_desc["app_elf"])
|
||||
if not os.path.exists(elf_file):
|
||||
raise FatalError("ELF file '%s' not found. You need to build & flash the project before running 'monitor', and the binary on the device must match the one in the build directory exactly. Try 'idf.py flash monitor'." % elf_file)
|
||||
idf_monitor = os.path.join(os.environ["IDF_PATH"], "tools/idf_monitor.py")
|
||||
monitor_args = [PYTHON, idf_monitor ]
|
||||
if args.port is not None:
|
||||
monitor_args += [ "-p", args.port ]
|
||||
monitor_args += [ "-b", project_desc["monitor_baud"] ]
|
||||
monitor_args += [ elf_file ]
|
||||
if "MSYSTEM" is os.environ:
|
||||
monitor_args = [ "winpty" ] + monitor_args
|
||||
_run_tool("idf_monitor", monitor_args, args.build_dir)
|
||||
|
||||
|
||||
def clean(action, args):
|
||||
if not os.path.isdir(args.build_dir):
|
||||
print("Build directory '%s' not found. Nothing to clean." % args.build_dir)
|
||||
return
|
||||
build_target("clean", args)
|
||||
|
||||
def reconfigure(action, args):
|
||||
_ensure_build_directory(args, True)
|
||||
|
||||
def fullclean(action, args):
|
||||
build_dir = args.build_dir
|
||||
if not os.path.isdir(build_dir):
|
||||
print("Build directory '%s' not found. Nothing to clean." % build_dir)
|
||||
return
|
||||
if len(os.listdir(build_dir)) == 0:
|
||||
print("Build directory '%s' is empty. Nothing to clean." % build_dir)
|
||||
return
|
||||
|
||||
if not os.path.exists(os.path.join(build_dir, "CMakeCache.txt")):
|
||||
raise FatalError("Directory '%s' doesn't seem to be a CMake build directory. Refusing to automatically delete files in this directory. Delete the directory manually to 'clean' it." % build_dir)
|
||||
red_flags = [ "CMakeLists.txt", ".git", ".svn" ]
|
||||
for red in red_flags:
|
||||
red = os.path.join(build_dir, red)
|
||||
if os.path.exists(red):
|
||||
raise FatalError("Refusing to automatically delete files in directory containing '%s'. Delete files manually if you're sure." % red)
|
||||
# OK, delete everything in the build directory...
|
||||
for f in os.listdir(build_dir): # TODO: once we are Python 3 only, this can be os.scandir()
|
||||
f = os.path.join(build_dir, f)
|
||||
if os.path.isdir(f):
|
||||
shutil.rmtree(f)
|
||||
else:
|
||||
os.remove(f)
|
||||
|
||||
ACTIONS = {
|
||||
# action name : ( function (or alias), dependencies, order-only dependencies )
|
||||
"all" : ( build_target, [], [ "reconfigure", "menuconfig", "clean", "fullclean" ] ),
|
||||
"build": ( "all", [], [] ), # build is same as 'all' target
|
||||
"clean": ( clean, [], [ "fullclean" ] ),
|
||||
"fullclean": ( fullclean, [], [] ),
|
||||
"reconfigure": ( reconfigure, [], [] ),
|
||||
"menuconfig": ( build_target, [], [] ),
|
||||
"size": ( build_target, [], [ "app" ] ),
|
||||
"size-components": ( build_target, [], [ "app" ] ),
|
||||
"size-files": ( build_target, [], [ "app" ] ),
|
||||
"bootloader": ( build_target, [], [] ),
|
||||
"bootloader-clean": ( build_target, [], [] ),
|
||||
"bootloader-flash": ( flash, [ "bootloader" ], [] ),
|
||||
"app": ( build_target, [], [ "clean", "fullclean", "reconfigure" ] ),
|
||||
"app-flash": ( flash, [], [ "app" ]),
|
||||
"partition_table": ( build_target, [], [ "reconfigure" ] ),
|
||||
"partition_table-flash": ( flash, [ "partition_table" ], []),
|
||||
"flash": ( flash, [ "all" ], [ ] ),
|
||||
"erase_flash": ( erase_flash, [], []),
|
||||
"monitor": ( monitor, [], [ "flash", "partition_table-flash", "bootloader-flash", "app-flash" ]),
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='ESP-IDF build management tool')
|
||||
parser.add_argument('-p', '--port', help="Serial port", default=None)
|
||||
parser.add_argument('-b', '--baud', help="Baud rate", default=460800)
|
||||
parser.add_argument('-C', '--project-dir', help="Project directory", default=os.getcwd())
|
||||
parser.add_argument('-B', '--build-dir', help="Build directory", default=None)
|
||||
parser.add_argument('-G', '--generator', help="Cmake generator", choices=GENERATOR_CMDS.keys())
|
||||
parser.add_argument('-n', '--no-warnings', help="Disable Cmake warnings", action="store_true")
|
||||
parser.add_argument('-v', '--verbose', help="Verbose build output", action="store_true")
|
||||
parser.add_argument('--no-ccache', help="Disable ccache. Otherwise, if ccache is available on the PATH then it will be used for faster builds.", action="store_true")
|
||||
parser.add_argument('actions', help="Actions (build targets or other operations)", nargs='+',
|
||||
choices=ACTIONS.keys())
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
check_environment()
|
||||
|
||||
# Advanced parameter checks
|
||||
if args.build_dir is not None and os.path.realpath(args.project_dir) == os.path.realpath(args.build_dir):
|
||||
raise FatalError("Setting the build directory to the project directory is not supported. Suggest dropping --build-dir option, the default is a 'build' subdirectory inside the project directory.")
|
||||
if args.build_dir is None:
|
||||
args.build_dir = os.path.join(args.project_dir, "build")
|
||||
args.build_dir = os.path.realpath(args.build_dir)
|
||||
|
||||
completed_actions = set()
|
||||
def execute_action(action, remaining_actions):
|
||||
( function, dependencies, order_dependencies ) = ACTIONS[action]
|
||||
# very simple dependency management, build a set of completed actions and make sure
|
||||
# all dependencies are in it
|
||||
for dep in dependencies:
|
||||
if not dep in completed_actions:
|
||||
execute_action(dep, remaining_actions)
|
||||
for dep in order_dependencies:
|
||||
if dep in remaining_actions and not dep in completed_actions:
|
||||
execute_action(dep, remaining_actions)
|
||||
|
||||
if action in completed_actions:
|
||||
pass # we've already done this, don't do it twice...
|
||||
elif function in ACTIONS: # alias of another action
|
||||
execute_action(function, remaining_actions)
|
||||
else:
|
||||
function(action, args)
|
||||
|
||||
completed_actions.add(action)
|
||||
|
||||
while len(args.actions) > 0:
|
||||
execute_action(args.actions[0], args.actions[1:])
|
||||
args.actions.pop(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except FatalError as e:
|
||||
print(e)
|
||||
sys.exit(2)
|
||||
|
||||
|
@ -2,6 +2,10 @@
|
||||
# Kernel configuration targets
|
||||
# These targets are used from top-level makefile
|
||||
|
||||
# SRCDIR is kconfig source dir, allows for out-of-tree builds
|
||||
# if building in tree, SRCDIR==build dir
|
||||
SRCDIR := $(abspath $(dir $(firstword $(MAKEFILE_LIST))))
|
||||
|
||||
PHONY += xconfig gconfig menuconfig config silentoldconfig \
|
||||
localmodconfig localyesconfig clean
|
||||
|
||||
@ -23,6 +27,22 @@ CFLAGS :=
|
||||
CPPFLAGS :=
|
||||
LDFLAGS :=
|
||||
|
||||
# Workaround for a bug on Windows if the mingw32 host compilers
|
||||
# are installed in addition to the MSYS ones. The kconfig tools
|
||||
# need to be compiled using the MSYS compiler.
|
||||
#
|
||||
# See https://github.com/espressif/esp-idf/issues/1296
|
||||
ifdef MSYSTEM
|
||||
ifeq ("$(MSYSTEM)", "MINGW32")
|
||||
ifeq ("$(CC)", "cc")
|
||||
CC := /usr/bin/gcc
|
||||
endif
|
||||
ifeq ("$(LD)", "ld")
|
||||
LD := /usr/bin/ld
|
||||
endif
|
||||
endif # MING32
|
||||
endif # MSYSTEM
|
||||
|
||||
default: mconf conf
|
||||
|
||||
xconfig: qconf
|
||||
@ -140,13 +160,22 @@ help:
|
||||
@echo ' tinyconfig - Configure the tiniest possible kernel'
|
||||
|
||||
# lxdialog stuff
|
||||
check-lxdialog := lxdialog/check-lxdialog.sh
|
||||
check-lxdialog := $(SRCDIR)/lxdialog/check-lxdialog.sh
|
||||
|
||||
# Use recursively expanded variables so we do not call gcc unless
|
||||
# we really need to do so. (Do not call gcc as part of make mrproper)
|
||||
CFLAGS += $(shell $(CONFIG_SHELL) $(check-lxdialog) -ccflags) \
|
||||
-DLOCALE -MD
|
||||
|
||||
%.o: $(SRCDIR)/%.c
|
||||
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
|
||||
|
||||
lxdialog/%.o: $(SRCDIR)/lxdialog/%.c
|
||||
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -I $(SRCDIR) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
|
||||
|
||||
# ===========================================================================
|
||||
# Shared Makefile for the various kconfig executables:
|
||||
# conf: Used for defconfig, oldconfig and related targets
|
||||
@ -183,9 +212,12 @@ clean-files += $(all-objs) $(all-deps) conf mconf
|
||||
# Check that we have the required ncurses stuff installed for lxdialog (menuconfig)
|
||||
PHONY += dochecklxdialog
|
||||
$(addprefix ,$(lxdialog)): dochecklxdialog
|
||||
dochecklxdialog:
|
||||
dochecklxdialog: lxdialog
|
||||
$(CONFIG_SHELL) $(check-lxdialog) -check $(CC) $(CFLAGS) $(LOADLIBES_mconf)
|
||||
|
||||
lxdialog:
|
||||
mkdir -p lxdialog
|
||||
|
||||
always := dochecklxdialog
|
||||
|
||||
# Add environment specific flags
|
||||
@ -292,7 +324,7 @@ gconf.glade.h: gconf.glade
|
||||
gconf.glade
|
||||
|
||||
|
||||
mconf: $(mconf-objs)
|
||||
mconf: lxdialog $(mconf-objs)
|
||||
$(CC) -o $@ $(mconf-objs) $(LOADLIBES_mconf)
|
||||
|
||||
conf: $(conf-objs)
|
||||
@ -300,15 +332,15 @@ conf: $(conf-objs)
|
||||
|
||||
zconf.tab.c: zconf.lex.c
|
||||
|
||||
zconf.lex.c: zconf.l
|
||||
flex -L -P zconf -o zconf.lex.c zconf.l
|
||||
zconf.lex.c: $(SRCDIR)/zconf.l
|
||||
flex -L -P zconf -o zconf.lex.c $<
|
||||
|
||||
zconf.hash.c: zconf.gperf
|
||||
zconf.hash.c: $(SRCDIR)/zconf.gperf
|
||||
# strip CRs on Windows systems where gperf will otherwise barf on them
|
||||
sed -E "s/\\x0D$$//" zconf.gperf | gperf -t --output-file zconf.hash.c -a -C -E -g -k '1,3,$$' -p -t
|
||||
sed -E "s/\\x0D$$//" $< | gperf -t --output-file zconf.hash.c -a -C -E -g -k '1,3,$$' -p -t
|
||||
|
||||
zconf.tab.c: zconf.y
|
||||
bison -t -l -p zconf -o zconf.tab.c zconf.y
|
||||
zconf.tab.c: $(SRCDIR)/zconf.y
|
||||
bison -t -l -p zconf -o zconf.tab.c $<
|
||||
|
||||
clean:
|
||||
rm -f $(clean-files)
|
||||
|
@ -1,5 +1,5 @@
|
||||
%option nostdinit noyywrap never-interactive full ecs
|
||||
%option 8bit nodefault perf-report perf-report
|
||||
%option 8bit perf-report perf-report
|
||||
%option noinput
|
||||
%x COMMAND HELP STRING PARAM
|
||||
%{
|
||||
|
206
tools/kconfig_new/confgen.py
Executable file
206
tools/kconfig_new/confgen.py
Executable file
@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Command line tool to take in ESP-IDF sdkconfig files with project
|
||||
# settings and output data in multiple formats (update config, generate
|
||||
# header file, generate .cmake include file, documentation, etc).
|
||||
#
|
||||
# Used internally by the ESP-IDF build system. But designed to be
|
||||
# non-IDF-specific.
|
||||
#
|
||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import json
|
||||
|
||||
import gen_kconfig_doc
|
||||
import kconfiglib
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='confgen.py v%s - Config Generation Tool' % __version__, prog=os.path.basename(sys.argv[0]))
|
||||
|
||||
parser.add_argument('--config',
|
||||
help='Project configuration settings',
|
||||
nargs='?',
|
||||
default=None)
|
||||
|
||||
parser.add_argument('--defaults',
|
||||
help='Optional project defaults file, used if --config file doesn\'t exist',
|
||||
nargs='?',
|
||||
default=None)
|
||||
|
||||
parser.add_argument('--create-config-if-missing',
|
||||
help='If set, a new config file will be saved if the old one is not found',
|
||||
action='store_true')
|
||||
|
||||
parser.add_argument('--kconfig',
|
||||
help='KConfig file with config item definitions',
|
||||
required=True)
|
||||
|
||||
parser.add_argument('--output', nargs=2, action='append',
|
||||
help='Write output file (format and output filename)',
|
||||
metavar=('FORMAT', 'FILENAME'),
|
||||
default=[])
|
||||
|
||||
parser.add_argument('--env', action='append', default=[],
|
||||
help='Environment to set when evaluating the config file', metavar='NAME=VAL')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
for fmt, filename in args.output:
|
||||
if not fmt in OUTPUT_FORMATS.keys():
|
||||
print("Format '%s' not recognised. Known formats: %s" % (fmt, OUTPUT_FORMATS))
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
args.env = [ (name,value) for (name,value) in ( e.split("=",1) for e in args.env) ]
|
||||
except ValueError:
|
||||
print("--env arguments must each contain =. To unset an environment variable, use 'ENV='")
|
||||
sys.exit(1)
|
||||
|
||||
for name, value in args.env:
|
||||
os.environ[name] = value
|
||||
|
||||
config = kconfiglib.Kconfig(args.kconfig)
|
||||
|
||||
if args.defaults is not None:
|
||||
# always load defaults first, so any items which are not defined in that config
|
||||
# will have the default defined in the defaults file
|
||||
if not os.path.exists(args.defaults):
|
||||
raise RuntimeError("Defaults file not found: %s" % args.defaults)
|
||||
config.load_config(args.defaults)
|
||||
|
||||
if args.config is not None:
|
||||
if os.path.exists(args.config):
|
||||
config.load_config(args.config)
|
||||
elif args.create_config_if_missing:
|
||||
print("Creating config file %s..." % args.config)
|
||||
config.write_config(args.config)
|
||||
elif args.default is None:
|
||||
raise RuntimeError("Config file not found: %s" % args.config)
|
||||
|
||||
for output_type, filename in args.output:
|
||||
temp_file = tempfile.mktemp(prefix="confgen_tmp")
|
||||
try:
|
||||
output_function = OUTPUT_FORMATS[output_type]
|
||||
output_function(config, temp_file)
|
||||
update_if_changed(temp_file, filename)
|
||||
finally:
|
||||
try:
|
||||
os.remove(temp_file)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def write_config(config, filename):
|
||||
CONFIG_HEADING = """#
|
||||
# Automatically generated file. DO NOT EDIT.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Configuration
|
||||
#
|
||||
"""
|
||||
config.write_config(filename, header=CONFIG_HEADING)
|
||||
|
||||
def write_header(config, filename):
|
||||
CONFIG_HEADING = """/*
|
||||
* Automatically generated file. DO NOT EDIT.
|
||||
* Espressif IoT Development Framework (ESP-IDF) Configuration Header
|
||||
*/
|
||||
#pragma once
|
||||
"""
|
||||
config.write_autoconf(filename, header=CONFIG_HEADING)
|
||||
|
||||
def write_cmake(config, filename):
|
||||
with open(filename, "w") as f:
|
||||
write = f.write
|
||||
prefix = config.config_prefix
|
||||
|
||||
write("""#
|
||||
# Automatically generated file. DO NOT EDIT.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Configuration cmake include file
|
||||
#
|
||||
""")
|
||||
def write_node(node):
|
||||
sym = node.item
|
||||
if not isinstance(sym, kconfiglib.Symbol):
|
||||
return
|
||||
|
||||
# Note: str_value calculates _write_to_conf, due to
|
||||
# internal magic in kconfiglib...
|
||||
val = sym.str_value
|
||||
if sym._write_to_conf:
|
||||
if sym.orig_type in (kconfiglib.BOOL, kconfiglib.TRISTATE) and val == "n":
|
||||
val = "" # write unset values as empty variables
|
||||
write("set({}{} \"{}\")\n".format(
|
||||
prefix, sym.name, val))
|
||||
config.walk_menu(write_node)
|
||||
|
||||
def write_json(config, filename):
|
||||
config_dict = {}
|
||||
|
||||
def write_node(node):
|
||||
sym = node.item
|
||||
if not isinstance(sym, kconfiglib.Symbol):
|
||||
return
|
||||
|
||||
val = sym.str_value # this calculates _write_to_conf, due to kconfiglib magic
|
||||
if sym._write_to_conf:
|
||||
if sym.type in [ kconfiglib.BOOL, kconfiglib.TRISTATE ]:
|
||||
val = (val != "n")
|
||||
elif sym.type == kconfiglib.HEX:
|
||||
val = int(val, 16)
|
||||
elif sym.type == kconfiglib.INT:
|
||||
val = int(val)
|
||||
config_dict[sym.name] = val
|
||||
config.walk_menu(write_node)
|
||||
with open(filename, "w") as f:
|
||||
json.dump(config_dict, f, indent=4, sort_keys=True)
|
||||
|
||||
def update_if_changed(source, destination):
|
||||
with open(source, "r") as f:
|
||||
source_contents = f.read()
|
||||
if os.path.exists(destination):
|
||||
with open(destination, "r") as f:
|
||||
dest_contents = f.read()
|
||||
if source_contents == dest_contents:
|
||||
return # nothing to update
|
||||
|
||||
with open(destination, "w") as f:
|
||||
f.write(source_contents)
|
||||
|
||||
|
||||
OUTPUT_FORMATS = {
|
||||
"config" : write_config,
|
||||
"header" : write_header,
|
||||
"cmake" : write_cmake,
|
||||
"docs" : gen_kconfig_doc.write_docs,
|
||||
"json" : write_json,
|
||||
}
|
||||
|
||||
class FatalError(RuntimeError):
|
||||
"""
|
||||
Class for runtime errors (not caused by bugs but by user input).
|
||||
"""
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except FatalError as e:
|
||||
print("A fatal error occurred: %s" % e)
|
||||
sys.exit(2)
|
137
tools/kconfig_new/gen_kconfig_doc.py
Normal file
137
tools/kconfig_new/gen_kconfig_doc.py
Normal file
@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# gen_kconfig_doc - confgen.py support for generating ReST markup documentation
|
||||
#
|
||||
# For each option in the loaded Kconfig (e.g. 'FOO'), CONFIG_FOO link target is
|
||||
# generated, allowing options to be referenced in other documents
|
||||
# (using :ref:`CONFIG_FOO`)
|
||||
#
|
||||
# Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import os
|
||||
import kconfiglib
|
||||
|
||||
# Indentation to be used in the generated file
|
||||
INDENT = ' '
|
||||
|
||||
# Characters used when underlining section heading
|
||||
HEADING_SYMBOLS = '#*=-^"+'
|
||||
|
||||
# Keep the heading level in sync with api-reference/kconfig.rst
|
||||
INITIAL_HEADING_LEVEL = 3
|
||||
MAX_HEADING_LEVEL = len(HEADING_SYMBOLS)-1
|
||||
|
||||
def write_docs(config, filename):
|
||||
""" Note: writing .rst documentation ignores the current value
|
||||
of any items. ie the --config option can be ignored.
|
||||
(However at time of writing it still needs to be set to something...) """
|
||||
with open(filename, "w") as f:
|
||||
config.walk_menu(lambda node: write_menu_item(f, node))
|
||||
|
||||
|
||||
def get_breadcrumbs(node):
|
||||
# this is a bit wasteful as it recalculates each time, but still...
|
||||
result = []
|
||||
node = node.parent
|
||||
while node.parent:
|
||||
if node.prompt:
|
||||
result = [ node.prompt[0] ] + result
|
||||
node = node.parent
|
||||
return " > ".join(result)
|
||||
|
||||
def get_heading_level(node):
|
||||
# bit wasteful also
|
||||
result = INITIAL_HEADING_LEVEL
|
||||
node = node.parent
|
||||
while node.parent:
|
||||
result += 1
|
||||
if result == MAX_HEADING_LEVEL:
|
||||
return MAX_HEADING_LEVEL
|
||||
node = node.parent
|
||||
return result
|
||||
|
||||
def format_rest_text(text, indent):
|
||||
# Format an indented text block for use with ReST
|
||||
text = indent + text.replace('\n', '\n' + indent)
|
||||
# Escape some characters which are inline formatting in ReST
|
||||
text = text.replace("*", "\\*")
|
||||
text = text.replace("_", "\\_")
|
||||
text += '\n'
|
||||
return text
|
||||
|
||||
def write_menu_item(f, node):
|
||||
if not node.prompt:
|
||||
return # Don't do anything for invisible menu items
|
||||
|
||||
if isinstance(node.parent.item, kconfiglib.Choice):
|
||||
return # Skip choice nodes, they are handled as part of the parent (see below)
|
||||
|
||||
try:
|
||||
name = node.item.name
|
||||
except AttributeError:
|
||||
name = None
|
||||
|
||||
try:
|
||||
is_menu = node.item == kconfiglib.MENU or node.is_menuconfig
|
||||
except AttributeError:
|
||||
is_menu = False # not all MenuNodes have is_menuconfig for some reason
|
||||
|
||||
## Heading
|
||||
if name:
|
||||
title = name
|
||||
# add link target so we can use :ref:`CONFIG_FOO`
|
||||
f.write('.. _CONFIG_%s:\n\n' % name)
|
||||
else:
|
||||
title = node.prompt[0]
|
||||
|
||||
# if no symbol name, use the prompt as the heading
|
||||
if True or is_menu:
|
||||
f.write('%s\n' % title)
|
||||
f.write(HEADING_SYMBOLS[get_heading_level(node)] * len(title))
|
||||
f.write('\n\n')
|
||||
else:
|
||||
f.write('**%s**\n\n\n' % title)
|
||||
|
||||
if name:
|
||||
f.write('%s%s\n\n' % (INDENT, node.prompt[0]))
|
||||
f.write('%s:emphasis:`Found in: %s`\n\n' % (INDENT, get_breadcrumbs(node)))
|
||||
|
||||
try:
|
||||
if node.help:
|
||||
# Help text normally contains newlines, but spaces at the beginning of
|
||||
# each line are stripped by kconfiglib. We need to re-indent the text
|
||||
# to produce valid ReST.
|
||||
f.write(format_rest_text(node.help, INDENT))
|
||||
except AttributeError:
|
||||
pass # No help
|
||||
|
||||
if isinstance(node.item, kconfiglib.Choice):
|
||||
f.write('%sAvailable options:\n' % INDENT)
|
||||
choice_node = node.list
|
||||
while choice_node:
|
||||
# Format available options as a list
|
||||
f.write('%s- %-20s (%s)\n' % (INDENT * 2, choice_node.prompt[0], choice_node.item.name))
|
||||
if choice_node.help:
|
||||
HELP_INDENT = INDENT * 2
|
||||
fmt_help = format_rest_text(choice_node.help, ' ' + HELP_INDENT)
|
||||
f.write('%s \n%s\n' % (HELP_INDENT, fmt_help))
|
||||
choice_node = choice_node.next
|
||||
|
||||
f.write('\n\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Run this via 'confgen.py --output doc FILENAME'")
|
||||
|
4317
tools/kconfig_new/kconfiglib.py
Normal file
4317
tools/kconfig_new/kconfiglib.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user