Merge branch 'chore/add_cmake_tools' into 'master'

Add cmake tools

See merge request sdk/ESP8266_RTOS_SDK!123
This commit is contained in:
Wu Jian Gang
2018-05-03 17:59:15 +08:00
22 changed files with 6757 additions and 11 deletions

View File

@ -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

View 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
View 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()

View 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()

View 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()

View 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
View 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
View 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()

View 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
View 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

View 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}")

View 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()")

View 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()

View 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()

View 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
View 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
View 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)

View File

@ -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)

View File

@ -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
View 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)

View 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'")

File diff suppressed because it is too large Load Diff