From b599f127b5e6ee9af12f4de20ac41e66fdf60e1c Mon Sep 17 00:00:00 2001 From: "martin.gano" Date: Thu, 3 Sep 2020 11:16:42 +0200 Subject: [PATCH] add new command to idf --- docs/en/api-guides/build-system.rst | 32 +++++ examples/get-started/hello_world/README.md | 2 +- .../get-started/sample_project/CMakeLists.txt | 7 + examples/get-started/sample_project/Makefile | 8 ++ examples/get-started/sample_project/README.md | 32 +++++ .../sample_project/main/CMakeLists.txt | 2 + .../sample_project/main/component.mk | 5 + .../get-started/sample_project/main/main.c | 6 + tools/ci/test_build_system_cmake.sh | 49 ++++++- tools/idf_py_actions/core_ext.py | 1 + tools/idf_py_actions/create_ext.py | 120 ++++++++++++++++++ .../templates/sample_component/CMakeLists.txt | 2 + .../templates/sample_component/include/main.h | 1 + tools/templates/sample_component/main.c | 7 + 14 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 examples/get-started/sample_project/CMakeLists.txt create mode 100644 examples/get-started/sample_project/Makefile create mode 100644 examples/get-started/sample_project/README.md create mode 100644 examples/get-started/sample_project/main/CMakeLists.txt create mode 100644 examples/get-started/sample_project/main/component.mk create mode 100644 examples/get-started/sample_project/main/main.c create mode 100644 tools/idf_py_actions/create_ext.py create mode 100644 tools/templates/sample_component/CMakeLists.txt create mode 100644 tools/templates/sample_component/include/main.h create mode 100644 tools/templates/sample_component/main.c diff --git a/docs/en/api-guides/build-system.rst b/docs/en/api-guides/build-system.rst index a84f414ece..9da4aa211f 100644 --- a/docs/en/api-guides/build-system.rst +++ b/docs/en/api-guides/build-system.rst @@ -117,6 +117,21 @@ Note that some older versions of CCache may exhibit bugs on some platforms, so i - ``-v`` flag causes both ``idf.py`` and the build system to produce verbose build output. This can be useful for debugging build problems. - ``--cmake-warn-uninitialized`` (or ``-w``) will cause CMake to print uninitialized variable warnings inside the project directory (not for directories not found inside the project directory). This only controls CMake variable warnings inside CMake itself, not other types of build warnings. This option can also be set permanently by setting the ``IDF_CMAKE_WARN_UNINITIALIZED`` environment variable to a non-zero value. + +Start a new project +------------------- + +Use the command ``idf.py create-project`` for starting a new project. Execute ``idf.py create-project --help`` for more information. + + +Example: + +.. code-block:: bash + + idf.py create-project --path my_projects my_new_project + +This example will create a new project called my_new_project directly into the directory my_projects. + Using CMake Directly -------------------- @@ -344,6 +359,23 @@ are discussed :ref:`here`. See `example component requirements`_ and `example component CMakeLists`_ for more complete component ``CMakeLists.txt`` examples. +Create a new component +---------------------- + +Use the command ``idf.py create-component`` for creating a new component. +The new component will contain set of files necessary for building a component. +You may include the component's header file into your project and use its functionality. +For more information execute ``idf.py create-component --help``. + +Example: + +.. code-block:: bash + + idf.py -C components create-component my_component + +The example will create a new component in the subdirectory `components` under the current working directory. +For more information about components follow the documentation page :ref:`see above `. + .. _component variables: Preset Component Variables diff --git a/examples/get-started/hello_world/README.md b/examples/get-started/hello_world/README.md index 0539ac7b24..7afca17e2f 100644 --- a/examples/get-started/hello_world/README.md +++ b/examples/get-started/hello_world/README.md @@ -18,7 +18,7 @@ Select the instructions depending on Espressif chip installed on your developmen The project **hello_world** contains one source file in C language [hello_world_main.c](main/hello_world_main.c). The file is located in folder [main](main). -ESP-IDF projects are build using CMake. The project build configuration is contained in `CMakeLists.txt` files that provide set of directives and instructions describing the project's source files and targets (executable, library, or both). +ESP-IDF projects are built using CMake. The project build configuration is contained in `CMakeLists.txt` files that provide set of directives and instructions describing the project's source files and targets (executable, library, or both). Below is short explanation of remaining files in the project folder. diff --git a/examples/get-started/sample_project/CMakeLists.txt b/examples/get-started/sample_project/CMakeLists.txt new file mode 100644 index 0000000000..30b64b99e0 --- /dev/null +++ b/examples/get-started/sample_project/CMakeLists.txt @@ -0,0 +1,7 @@ +# For more information about build system see https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five 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) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(main) diff --git a/examples/get-started/sample_project/Makefile b/examples/get-started/sample_project/Makefile new file mode 100644 index 0000000000..da4d16c6aa --- /dev/null +++ b/examples/get-started/sample_project/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := sample_project + +include $(IDF_PATH)/make/project.mk diff --git a/examples/get-started/sample_project/README.md b/examples/get-started/sample_project/README.md new file mode 100644 index 0000000000..455eb90909 --- /dev/null +++ b/examples/get-started/sample_project/README.md @@ -0,0 +1,32 @@ +# _Sample project_ + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This is the simplest buildable example. The example is used by command `idf.py create-project` +that copies the project to user specified path and set it's name. For more information follow the [docs page](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#start-a-new-project) + + + +## How to use example +We encourage the users to use the example as a template for the new projects. +A recommended way is to follow the instructions on a [docs page](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#start-a-new-project). + +## Example folder contents + +The project **sample_project** contains one source file in C language [main.c](main/main.c). The file is located in folder [main](main). + +ESP-IDF projects are built using CMake. The project build configuration is contained in `CMakeLists.txt` +files that provide set of directives and instructions describing the project's source files and targets +(executable, library, or both). + +Below is short explanation of remaining files in the project folder. + +``` +├── CMakeLists.txt +├── main +│   ├── CMakeLists.txt +│   └── main.c +└── README.md This is the file you are currently reading +``` +Additionally, the sample project contains Makefile and component.mk files, used for the legacy Make based build system. +They are not used or needed when building with CMake and idf.py. diff --git a/examples/get-started/sample_project/main/CMakeLists.txt b/examples/get-started/sample_project/main/CMakeLists.txt new file mode 100644 index 0000000000..cf2c455cb5 --- /dev/null +++ b/examples/get-started/sample_project/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS ".") diff --git a/examples/get-started/sample_project/main/component.mk b/examples/get-started/sample_project/main/component.mk new file mode 100644 index 0000000000..0b9d7585e7 --- /dev/null +++ b/examples/get-started/sample_project/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/examples/get-started/sample_project/main/main.c b/examples/get-started/sample_project/main/main.c new file mode 100644 index 0000000000..7b66f33939 --- /dev/null +++ b/examples/get-started/sample_project/main/main.c @@ -0,0 +1,6 @@ +#include + +void app_main(void) +{ + +} diff --git a/tools/ci/test_build_system_cmake.sh b/tools/ci/test_build_system_cmake.sh index 911d5b49ca..72c6769a10 100755 --- a/tools/ci/test_build_system_cmake.sh +++ b/tools/ci/test_build_system_cmake.sh @@ -758,7 +758,7 @@ endmenu\n" >> ${IDF_PATH}/Kconfig clean_build_dir mkdir -p components/esp32 echo "idf_component_get_property(overriden_dir \${COMPONENT_NAME} COMPONENT_OVERRIDEN_DIR)" >> components/esp32/CMakeLists.txt - echo "message(STATUS overriden_dir:\${overriden_dir})" >> components/esp32/CMakeLists.txt + echo "message(STATUS overriden_dir:\${overriden_dir})" >> components/esp32/CMakeLists.txt (idf.py reconfigure | grep "overriden_dir:$IDF_PATH/components/esp32") || failure "Failed to get overriden dir" # no registration, overrides registration as well print_status "Overriding Kconfig" echo "idf_component_register(KCONFIG \${overriden_dir}/Kconfig)" >> components/esp32/CMakeLists.txt @@ -767,6 +767,44 @@ endmenu\n" >> ${IDF_PATH}/Kconfig (idf.py reconfigure | grep "kconfig:$IDF_PATH/components/esp32/Kconfig") || failure "Failed to verify original `main` directory" rm -rf components + print_status "Create project using idf.py and build it" + echo "Trying to create project." + (idf.py -C projects create-project temp_test_project) || failure "Failed to create the project." + cd "$IDF_PATH/projects/temp_test_project" + echo "Building the project temp_test_project . . ." + idf.py build || failure "Failed to build the project." + cd "$IDF_PATH" + rm -rf "$IDF_PATH/projects/temp_test_project" + + print_status "Create component using idf.py, create project using idf.py." + print_status "Add the component to the created project and build the project." + echo "Trying to create project . . ." + (idf.py -C projects create-project temp_test_project) || failure "Failed to create the project." + echo "Trying to create component . . ." + (idf.py -C components create-component temp_test_component) || failure "Failed to create the component." + ${SED} -i '5i\\tfunc();' "$IDF_PATH/projects/temp_test_project/main/temp_test_project.c" + ${SED} -i '5i#include "temp_test_component.h"' "$IDF_PATH/projects/temp_test_project/main/temp_test_project.c" + cd "$IDF_PATH/projects/temp_test_project" + idf.py build || failure "Failed to build the project." + cd "$IDF_PATH" + rm -rf "$IDF_PATH/projects/temp_test_project" + rm -rf "$IDF_PATH/components/temp_test_component" + + print_status "Check that command for creating new project will fail if the target folder is not empty." + mkdir "$IDF_PATH/example_proj/" + touch "$IDF_PATH/example_proj/tmp_130698" + EXPECTED_EXIT_VALUE=3 + expected_failure $EXPECTED_EXIT_VALUE idf.py create-project --path "$IDF_PATH/example_proj/" temp_test_project || failure "Command exit value is wrong." + rm -rf "$IDF_PATH/example_proj/" + + print_status "Check that command for creating new project will fail if the target path is file." + touch "$IDF_PATH/example_proj" + EXPECTED_EXIT_VALUE=4 + expected_failure $EXPECTED_EXIT_VALUE idf.py create-project --path "$IDF_PATH/example_proj" temp_test_project || failure "Command exit value is wrong." + rm -rf "$IDF_PATH/example_proj" + + + print_status "All tests completed" if [ -n "${FAILURES}" ]; then echo "Some failures were detected:" @@ -791,6 +829,15 @@ function failure() FAILURES="${FAILURES}${STATUS} :: $1\n" } +function expected_failure() { + "${@:2}" + EXIT_VALUE=$? + if [ $EXIT_VALUE != "$1" ]; then + echo "[ERROR] Exit value of executed command is $EXIT_VALUE (expected $1)"; return 1 + else return 0 + fi +} + TESTDIR=${PWD}/build_system_tests_$$ mkdir -p ${TESTDIR} # set NOCLEANUP=1 if you want to keep the test directory around diff --git a/tools/idf_py_actions/core_ext.py b/tools/idf_py_actions/core_ext.py index 78d3a25348..a8bb28b9a8 100644 --- a/tools/idf_py_actions/core_ext.py +++ b/tools/idf_py_actions/core_ext.py @@ -210,6 +210,7 @@ def action_extensions(base_actions, project_path): }, { "names": ["-C", "--project-dir"], + "scope": "shared", "help": "Project directory.", "type": click.Path(), "default": os.getcwd(), diff --git a/tools/idf_py_actions/create_ext.py b/tools/idf_py_actions/create_ext.py new file mode 100644 index 0000000000..bc06ba9014 --- /dev/null +++ b/tools/idf_py_actions/create_ext.py @@ -0,0 +1,120 @@ +from __future__ import print_function +from distutils.dir_util import copy_tree + +import os +import re +import sys + + +def get_type(action): + return action.split("-")[1] + + +def replace_in_file(filename, pattern, replacement): + with open(filename, 'r+') as f: + content = f.read() + overwritten_content = re.sub(pattern, replacement, content, flags=re.M) + f.seek(0) + f.write(overwritten_content) + f.truncate() + + +def is_empty_and_create(path, action): + abspath = os.path.abspath(path) + if not os.path.exists(abspath): + os.makedirs(abspath) + elif not os.path.isdir(abspath): + print("Your target path is not a directory. Please remove the", os.path.abspath(abspath), + "or use different target path.") + sys.exit(4) + elif len(os.listdir(path)) > 0: + print("The directory", abspath, "is not empty. To create a", get_type(action), + "you must empty the directory or choose a different path.") + sys.exit(3) + + +def create_project(target_path, name): + copy_tree(os.path.join(os.environ['IDF_PATH'], "examples", "get-started", "sample_project"), target_path) + main_folder = os.path.join(target_path, "main") + os.rename(os.path.join(main_folder, "main.c"), os.path.join(main_folder, ".".join((name, "c")))) + replace_in_file(os.path.join(main_folder, "CMakeLists.txt"), "main", name) + replace_in_file(os.path.join(target_path, "CMakeLists.txt"), "main", name) + os.remove(os.path.join(target_path, "README.md")) + + # after manual removing "Makefile" and "component.mk" from `examples/get-started/sample_project` + # remove following two lines as well + os.remove(os.path.join(target_path, "Makefile")) + os.remove(os.path.join(target_path, "main", "component.mk")) + + +def create_component(target_path, name): + copy_tree(os.path.join(os.environ['IDF_PATH'], "tools", "templates", "sample_component"), target_path) + os.rename(os.path.join(target_path, "main.c"), os.path.join(target_path, ".".join((name, "c")))) + os.rename(os.path.join(target_path, "include", "main.h"), + os.path.join(target_path, "include", ".".join((name, "h")))) + + replace_in_file(os.path.join(target_path, ".".join((name, "c"))), "main", name) + replace_in_file(os.path.join(target_path, "CMakeLists.txt"), "main", name) + + +def action_extensions(base_actions, project_path): + def create_new(action, ctx, global_args, **action_args): + target_path = action_args.get('path') or os.path.join(project_path, action_args['name']) + + is_empty_and_create(target_path, action) + + func_action_map = {"create-project": create_project, "create-component": create_component} + func_action_map[action](target_path, action_args['name']) + + print("The", get_type(action), "was created in", os.path.abspath(target_path)) + + # after the command execution, no other commands are accepted and idf.py terminates + sys.exit(0) + + return { + "actions": { + "create-project": { + "callback": create_new, + "short_help": "Create a new project.", + "help": ("Create a new project with the name NAME specified as argument. " + "For example: " + "`idf.py create-project new_proj` " + "will create a new project in subdirectory called `new_proj` " + "of the current working directory. " + "For specifying the new project's path, use either the option --path for specifying the " + "destination directory, or the global option -C if the project should be created as a " + "subdirectory of the specified directory. " + "If the target path does not exist it will be created. If the target folder is not empty " + "then the operation will fail with return code 3. " + "If the target path is not a folder, the script will fail with return code 4. " + "After the execution idf.py terminates " + "so this operation should be used alone."), + "arguments": [{"names": ["name"]}], + "options": [ + { + "names": ["-p", "--path"], + "help": ("Set the path for the new project. The project " + "will be created directly in the given folder if it does not contain anything"), + }, + ], + + }, + "create-component": { + "callback": create_new, + "short_help": "Create a new component.", + "help": ("Create a new component with the name NAME specified as argument. " + "For example: " + "`idf.py create-component new_comp` " + "will create a new component in subdirectory called `new_comp` " + "of the current working directory. " + "For specifying the new component's path use the option -C. " + "If the target path does not exist then it will be created. " + "If the target folder is not empty " + "then the operation will fail with return code 3. " + "If the target path is not a folder, the script will fail with return code 4. " + "After the execution idf.py terminates " + "so this operation should be used alone."), + "arguments": [{"names": ["name"]}], + } + } + } diff --git a/tools/templates/sample_component/CMakeLists.txt b/tools/templates/sample_component/CMakeLists.txt new file mode 100644 index 0000000000..d97a97c4b8 --- /dev/null +++ b/tools/templates/sample_component/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS "include") diff --git a/tools/templates/sample_component/include/main.h b/tools/templates/sample_component/include/main.h new file mode 100644 index 0000000000..f6f3a613cd --- /dev/null +++ b/tools/templates/sample_component/include/main.h @@ -0,0 +1 @@ +void func(void); diff --git a/tools/templates/sample_component/main.c b/tools/templates/sample_component/main.c new file mode 100644 index 0000000000..a4c8c611c5 --- /dev/null +++ b/tools/templates/sample_component/main.c @@ -0,0 +1,7 @@ +#include +#include "main.h" + +void func(void) +{ + +}