Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scripts: add Zephyr inline code generation with Python #6762

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e4300b1
cmake: Add zephyr base address when looking file into soc/
erwango Sep 13, 2018
7bacaa7
dts/bindings: Add 'generation' directive on for 'compatible' property
erwango Aug 20, 2018
6dc78f8
scripts/dts: Introduce edts.json generation
b0661 Sep 6, 2018
625ba19
scripts: extract_dts_includes.py: create EDTS database for codegen
b0661 Sep 6, 2018
dba3f36
scripts: extract_dts_includes.py: extract compatible to EDTS database
b0661 Jun 15, 2018
40fc54c
scripts: extract_dts_includes.py: extract reg to EDTS database
b0661 Aug 19, 2018
dc1cc90
scripts: extract_dts_includes.py: extract interrupts to EDTS database
b0661 Aug 19, 2018
f3d9cb3
scripts: extract_dts_includes.py: extract clocks to EDTS database
b0661 Aug 19, 2018
800bd18
scripts: extract_dts_includes.py: default extract to EDTS database
b0661 Aug 19, 2018
8e2b0d3
scripts: extract_dts_includes.py: extract heuristics to EDTS database
b0661 Aug 19, 2018
4d82a85
scripts/dts: extract_dts_includes: remove unused 'names' argument
erwango Sep 10, 2018
16e4458
scripts: extract_dts_includes: extract gpios to EDTS database
erwango Sep 10, 2018
baa92cf
dts/bindings: Replace id with generic zephyr type
erwango Sep 11, 2018
0662005
scripts: extract_dts_includes: simplify extraction of 'id' yaml prop
erwango Sep 11, 2018
2a5103a
dts/bindings: Rename 'id' yaml property into 'type'
erwango Sep 11, 2018
8cd8ea7
scripts/edts: inject device-type in edts database.
erwango Sep 13, 2018
aa01d40
scripts: codegen: add inline code generation
b0661 Feb 28, 2018
ee916cf
doc: codegen: add inline code generation documentation
b0661 Mar 10, 2018
a94415b
scripts: codegen: add generic module
b0661 Jun 1, 2018
96e5ff5
doc: codegen: add modules and templates doc
b0661 Jun 1, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ add_subdirectory(misc)
# property which is set implicitly for custom command outputs
include(misc/generated/CMakeLists.txt)

if(EXISTS soc/${ARCH}/CMakeLists.txt)
if(EXISTS ${ZEPHYR_BASE}/soc/${ARCH}/CMakeLists.txt)
add_subdirectory(${SOC_DIR}/${ARCH} soc/${ARCH})
else()
add_subdirectory(${SOC_DIR}/${ARCH}/${SOC_PATH} soc/${ARCH}/${SOC_PATH})
Expand All @@ -348,6 +348,13 @@ add_subdirectory(subsys)
add_subdirectory(drivers)
add_subdirectory(tests)

# Add all generated zephyr sources in the same context as the zephyr library
# is created. Assure all sub directories that might invoke code generation
# are processed before.
get_property(zephyr_generated_sources GLOBAL PROPERTY zephyr_generated_sources_property)
set_source_files_properties(${zephyr_generated_sources} PROPERTIES GENERATED 1)
target_sources(zephyr PRIVATE ${zephyr_generated_sources})

set(syscall_macros_h ${ZEPHYR_BINARY_DIR}/include/generated/syscall_macros.h)

add_custom_target(syscall_macros_h_target DEPENDS ${syscall_macros_h})
Expand Down
2 changes: 2 additions & 0 deletions cmake/dts.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# See ~/zephyr/doc/dts
set(GENERATED_DTS_BOARD_H ${PROJECT_BINARY_DIR}/include/generated/generated_dts_board.h)
set(GENERATED_DTS_BOARD_CONF ${PROJECT_BINARY_DIR}/include/generated/generated_dts_board.conf)
set(GENERATED_EDTS ${PROJECT_BINARY_DIR}/edts.json)
set_ifndef(DTS_SOURCE ${BOARD_ROOT}/boards/${ARCH}/${BOARD_FAMILY}/${BOARD}.dts)
set_ifndef(DTS_COMMON_OVERLAYS ${ZEPHYR_BASE}/dts/common/common.dts)
set_ifndef(DTS_APP_BINDINGS ${APPLICATION_SOURCE_DIR}/dts/bindings)
Expand Down Expand Up @@ -113,6 +114,7 @@ if(CONFIG_HAS_DTS)
${DTS_FIXUPS}
--keyvalue ${GENERATED_DTS_BOARD_CONF}
--include ${GENERATED_DTS_BOARD_H}
--edts ${GENERATED_EDTS}
)

# Run extract_dts_includes.py to create a .conf and a header file that can be
Expand Down
149 changes: 138 additions & 11 deletions cmake/extensions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,17 @@ function(generate_inc_file_for_gen_target
add_dependencies(${target} ${gen_target})
endfunction()

function(get_unique_generated_target_name
generated_file # The generated file
generated_target_name # the unique name
)
string(RANDOM LENGTH 8 random_chars)
get_filename_component(basename ${generated_file} NAME)
string(REPLACE "." "_" basename ${basename})
string(REPLACE "@" "_" basename ${basename})
set(generated_target_name "gen_${basename}_${random_chars}" PARENT_SCOPE)
endfunction()

function(generate_inc_file_for_target
target # The cmake target that depends on the generated file
source_file # The source file to be converted to hex
Expand All @@ -353,22 +364,131 @@ function(generate_inc_file_for_target
# Ensure 'generated_file' is generated before 'target' by creating a
# 'custom_target' for it and setting up a dependency between the two
# targets
get_unique_generated_target_name(${generated_file} generated_target_name)
add_custom_target(${generated_target_name} DEPENDS ${generated_file})
generate_inc_file_for_gen_target(${target} ${source_file} ${generated_file}
${generated_target_name} ${ARGN})
endfunction()

# But first create a unique name for the custom target
string(
RANDOM
LENGTH 8
random_chars
function(target_sources_codegen
target # The cmake target that depends on the generated file
)
set(options)
set(oneValueArgs)
set(multiValueArgs CODEGEN_DEFINES DEPENDS)
cmake_parse_arguments(CODEGEN "${options}" "${oneValueArgs}"
"${multiValueArgs}" ${ARGN})
# prepend -D to all defines
string(REGEX REPLACE "([^;]+)" "-D;\\1"
CODEGEN_CODEGEN_DEFINES "${CODEGEN_CODEGEN_DEFINES}")
# Get all the files that make up codegen for dependency
file(GLOB CODEGEN_SOURCES
${ZEPHYR_BASE}/scripts/dts/edtsdatabase.py
${ZEPHYR_BASE}/scripts/codegen/modules/*.py
${ZEPHYR_BASE}/scripts/codegen/templates/drivers/*.py
${ZEPHYR_BASE}/scripts/codegen/*.py
${ZEPHYR_BASE}/scripts/gen_code.py)

message(STATUS "Will generate for target ${target}")
# Generated file must be generated to the current binary directory.
# Otherwise this would trigger CMake issue #14633:
# https://gitlab.kitware.com/cmake/cmake/issues/14633
foreach(arg ${CODEGEN_UNPARSED_ARGUMENTS})
if(IS_ABSOLUTE ${arg})
set(template_file ${arg})
get_filename_component(generated_file_name ${arg} NAME)
set(generated_file ${CMAKE_CURRENT_BINARY_DIR}/${generated_file_name})
else()
set(template_file ${CMAKE_CURRENT_SOURCE_DIR}/${arg})
set(generated_file ${CMAKE_CURRENT_BINARY_DIR}/${arg})
endif()
get_filename_component(template_dir ${template_file} DIRECTORY)

get_filename_component(basename ${generated_file} NAME)
string(REPLACE "." "_" basename ${basename})
string(REPLACE "@" "_" basename ${basename})
if(IS_DIRECTORY ${template_file})
message(FATAL_ERROR "target_sources_codegen() was called on a directory")
endif()

set(generated_target_name "gen_${basename}_${random_chars}")
# Generate file from template
message(STATUS " from '${template_file}'")
message(STATUS " to '${generated_file}'")
add_custom_command(
COMMENT "CodeGen ${generated_file}"
OUTPUT ${generated_file}
MAIN_DEPENDENCY ${template_file}
DEPENDS
${CODEGEN_DEPENDS}
${CODEGEN_SOURCES}
COMMAND
${PYTHON_EXECUTABLE}
${ZEPHYR_BASE}/scripts/gen_code.py
${CODEGEN_CODEGEN_DEFINES}
-D "PROJECT_NAME=${PROJECT_NAME}"
-D "PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}"
-D "PROJECT_BINARY_DIR=${PROJECT_BINARY_DIR}"
-D "CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}"
-D "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}"
-D "CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}"
-D "CMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}"
-D "CMAKE_CURRENT_LIST_DIR=${CMAKE_CURRENT_LIST_DIR}"
-D "CMAKE_FILES_DIRECTORY=${CMAKE_FILES_DIRECTORY}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users must be able to specify additional CMake variables that
should be imported into the codegen namespace.

E.g. to generate offsets.h, it is necessary to know the path of offsets.o.

Also, we might want to cut down on the default list to minimize scope. But it is not clear to me
whether this should be done or not.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can´t you use the inline code to get this information?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of a way for the inline code to deduce this, so no, not without duplicating this information, which I would rather not do.

The build system knows where the file is and should pass this information on to the script. When things are organized like this the scope is minimized and it is easier to understand what the script is doing. (Because we can see exactly what kind of information is fed to it).

-D "CMAKE_PROJECT_NAME=${CMAKE_PROJECT_NAME}"
-D "CMAKE_SYSTEM=${CMAKE_SYSTEM}"
-D "CMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}"
-D "CMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}"
-D "CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}"
-D "CMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
-D "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
-D "CMAKE_COMPILER_IS_GNUCC=${CMAKE_COMPILER_IS_GNUCC}"
-D "CMAKE_COMPILER_IS_GNUCXX=${CMAKE_COMPILER_IS_GNUCXX}"
-D "GENERATED_DTS_BOARD_H=${GENERATED_DTS_BOARD_H}"
-D "GENERATED_DTS_BOARD_CONF=${GENERATED_DTS_BOARD_CONF}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generate files at build time and codegen is using cmake.py to parse CMakeCache.txt so I assume it is now unnecessary to explicitly pass on these variables?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variables are not in CMakeCache.txt.

-D "GENERATED_DTS_BOARD_EDTS=${GENERATED_DTS_BOARD_EDTS}"
--input "${template_file}"
--output "${generated_file}"
--log "${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/CodeGen.log"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
if("${target}" STREQUAL "zephyr")
# zephyr is special
get_unique_generated_target_name(${generated_file} generated_target_name)
add_custom_target(${generated_target_name} DEPENDS ${generated_file})
add_dependencies(zephyr ${generated_target_name})
# Remember all the files that are generated for zephyr.
# target_sources(zephyr PRIVATE ${zephyr_generated_sources})
# is executed in the top level CMakeFile.txt context.
get_property(zephyr_generated_sources GLOBAL PROPERTY zephyr_generated_sources_property)
list(APPEND zephyr_generated_sources ${generated_file})
set_property(GLOBAL PROPERTY zephyr_generated_sources_property "${zephyr_generated_sources}")
# Add template directory to include path to allow includes with
# relative path in generated file to work
zephyr_include_directories(${template_dir})
else()
target_sources(${target} PRIVATE ${generated_file})
# Add template directory to include path to allow includes with
# relative path in generated file to work
target_include_directories(${target} PRIVATE ${template_dir})
endif()
endforeach()
endfunction()

add_custom_target(${generated_target_name} DEPENDS ${generated_file})
generate_inc_file_for_gen_target(${target} ${source_file} ${generated_file} ${generated_target_name} ${ARGN})
function(zephyr_sources_codegen)
target_sources_codegen(zephyr ${ARGN})
endfunction()

function(zephyr_sources_codegen_ifdef feature_toggle)
if(${${feature_toggle}})
zephyr_sources_codegen(${ARGN})
endif()
endfunction()

function(zephyr_library_sources_codegen)
target_sources_codegen(${ZEPHYR_CURRENT_LIBRARY} ${ARGN})
endfunction()

function(zephyr_library_sources_codegen_ifdef feature_toggle)
if(${${feature_toggle}})
zephyr_library_sources_codegen(${ARGN})
endif()
endfunction()

# 1.2 zephyr_library_*
Expand Down Expand Up @@ -424,6 +544,13 @@ macro(zephyr_library_named name)
# This is a macro because we need add_library() to be executed
# within the scope of the caller.
set(ZEPHYR_CURRENT_LIBRARY ${name})

if("${name}" STREQUAL "zephyr")
# We have to mark all the generated files "GENERATED" in this context
get_property(zephyr_generated_files GLOBAL PROPERTY zephyr_generated_files_property)
set_source_files_properties(${zephyr_generated_files} PROPERTIES GENERATED 1)
endif()

add_library(${name} STATIC "")

zephyr_append_cmake_library(${name})
Expand Down
24 changes: 23 additions & 1 deletion doc/contribute/contribute_guidelines.rst
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,11 @@ On Linux systems, you can install uncrustify with
For Windows installation instructions see the `sourceforge listing for
uncrustify <https://sourceforge.net/projects/uncrustify>`_.

Best coding practises
*********************

Coding Style
************
============

Use these coding guidelines to ensure that your development complies with the
project's style and naming conventions.
Expand Down Expand Up @@ -363,6 +366,25 @@ it to contain:
set -e exec
exec git diff --cached | ${ZEPHYR_BASE}/scripts/checkpatch.pl -

Keep the code simple
====================

Keep the code as simple as possible.

Code-generation preprocessing tools provide a convenient way to
simplify some repetitive or parameterized coding tasks. While Zephyr
development allows use of such tools, we recommend keeping this
use to a minimum and when it provides an appropriate and simple
coding solution that follows these rules:

* Use code generation - by preprocessor, :ref:`codegen`, or other - only for
problems that cannot be solved in the source language.
* Limit code generation to declarative data. Avoid generating control logic
whenever possible.
* Use the preprocessor for code generation as the primary tool.
* Use :ref:`codegen` only if the preprocessor can not provide a simple solution.
* Use CMake only if :ref:`codegen` can not be used.

.. _Contribution workflow:

Contribution Workflow
Expand Down
29 changes: 29 additions & 0 deletions doc/subsystems/codegen/build.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
..
Copyright (c) 2018 Bobby Noelte
SPDX-License-Identifier: Apache-2.0

.. _codegen_build:

Code Generation in the Build Process
####################################

Code generation has to be invoked as part of the build process. Zephyr uses
`CMake <https://cmake.org/>`_ as the tool to manage building the project.

A file that contains inline code generation has to be added to the project
by one of the following commands in a :file:`CMakeList.txt` file.

.. function:: zephyr_sources_codegen(codegen_file.c [CODEGEN_DEFINES defines..] [DEPENDS target.. file..])

.. function:: zephyr_sources_codegen_ifdef(ifguard codegen_file.c [CODEGEN_DEFINES defines..] [DEPENDS target.. file..])

.. function:: zephyr_library_sources_codegen(codegen_file.c [CODEGEN_DEFINES defines..] [DEPENDS target.. file..])

.. function:: zephyr_library_sources_codegen_ifdef(ifguard codegen_file.c [CODEGEN_DEFINES defines..] [DEPENDS target.. file..])

The arguments given by the ``CODEGEN_DEFINES`` keyword have to be of the form
``define_name=define_value``. The arguments become globals in the python
snippets and can be accessed by ``define_name``.

Dependencies given by the ``DEPENDS`` key word are added to the dependencies
of the generated file.
90 changes: 90 additions & 0 deletions doc/subsystems/codegen/codegen.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
..
Copyright (c) 2004-2015 Ned Batchelder
SPDX-License-Identifier: MIT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nashif Will we need to account for this use of MIT licensed material?

Copyright (c) 2018 Bobby Noelte
SPDX-License-Identifier: Apache-2.0

.. _codegen_intro:

Introduction
############

Python snippets that are inlined in a source file are used as code generators.
The tool to scan the source file for the Python snippets and process them is
Codegen. Codegen and part of this documentation is based on
`Cog <https://nedbatchelder.com/code/cog/index.html>`_ from Ned Batchelder.

The processing of source files is controlled by the CMake extension functions:
zephyr_sources_codegen(..) or zephyr_library_sources_codegen(..). The generated
source files are added to the Zephyr sources. During build the source files are
processed by Codegen and the generated source files are written to the CMake
binary directory.

The inlined Python snippets can contain any Python code, they are regular
Python scripts. All Python snippets in a source file and all Python snippets of
included template files are treated as a python script with a common set of
global Python variables. Global data created in one snippet can be used in
another snippet that is processed later on. This feature could be used, for
example, to customize included template files.

An inlined Python snippet can always access the codegen module. The codegen
module encapsulates and provides all the functions to retrieve information
(options, device tree properties, CMake variables, config properties) and to
output the generated code.

Codegen transforms files in a very simple way: it finds chunks of Python code
embedded in them, executes the Python code, and places its output combined with
the original file into the generated file. The original file can contain
whatever text you like around the Python code. It will usually be source code.

For example, if you run this file through Codegen:

::

/* This is my C file. */
...
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... will this conflict with doxygen processing (that also uses the /** comment convention.

Copy link
Collaborator Author

@b0661 b0661 Mar 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not conflict with doxygen. In this example it is by purpose to include the python code snippet into doxygen output. The markers for code @code{.codegen}, .. are chosen in a way to support documentation by doxygen. /** is a marker for doxygen only and can be replaced by /* to leave the code snippet out of doxygen output..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I'm curious how this will play with the document generating process, and if we'll need to add a codegen pass over source files before presenting them to doxygen.

Copy link
Collaborator Author

@b0661 b0661 Mar 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how this will play with the document generating process

Currently none of the files is parsed by doxygen because the driver directory is not included in the doxygen documentation. make htmldocs works without any errors.

if we'll need to add a codegen pass over source

There is no need for an extra pass. Inline code is within comments and clearly marked with the doxygen special commands @code/@Endcode. Doxygen does not know the {.codegen} type. It ignores the unknown code type specification (see https://www.stack.nl/~dimitri/doxygen/manual/commands.html#cmdcode) and "just show the output as-is".

As an improvement {.codegen} could be made known to doxygen to be treated as python code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying!

* @code{.codegen}
* fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
* for fn in fnames:
* codegen.outl("void %s();" % fn)
* @endcode{.codegen}
*/
/** @code{.codeins}@endcode */
...

it will come out like this:

::

/* This is my C file. */
...
/**
* @code{.codegen}
* fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
* for fn in fnames:
* codegen.outl("void %s();" % fn)
* @endcode{.codegen}
*/
void DoSomething();
void DoAnotherThing();
void DoLastThing();
/** @code{.codeins}@endcode */
...

Lines with ``@code{.codegen}`` or ``@code{.codeins}@endcode`` are marker lines.
The lines between ``@code{.codegen}`` and ``@endcode{.codegen}`` are the
generator Python code. The lines between ``@endcode{.codegen}`` and
``@code{.codeins}@endcode`` are the output from the generator.

When Codegen runs, it discards the last generated Python output, executes the
generator Python code, and writes its generated output into the file. All text
lines outside of the special markers are passed through unchanged.

The Codegen marker lines can contain any text in addition to the marker tokens.
This makes it possible to hide the generator Python code from the source file.

In the sample above, the entire chunk of Python code is a C comment, so the
Python code can be left in place while the file is treated as C code.


Binary file added doc/subsystems/codegen/codegen_principle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading