blob: e12bf40f100cff5e59bb513b52c6e5d46a2acb92 [file] [log] [blame] [view]
# Code generation
## Code generation inputs (`*.zap` files)
Matter code relies on code generation for cluster-specific data types and
callbacks. Generally this is split into:
- Data serialization for structures/lists/commands. This applies to both
client-side and server-side structures and objects
- Callback setup using the Ember-based framework. This generally applies to
server-side processing and the code generation defines what processing needs
to be done when a specific command is received or an attribute is read and
what memory should be allocated for storing cluster attributes
Code generation depends on the clusters that are needed by an application. Every
application configures the specific set of endpoints and clusters it needs based
on the device type it supports. The selection of the supported clusters and
attributes (as optional attributes may be omitted to save memory) is generally
stored in `*.zap` files.
The selection of enabled clusters and files is done using
[ZAP](https://github.com/project-chip/zap). You can download a recent release of
zap from its [releases page](https://github.com/project-chip/zap/releases). It
is recommended to download a release that is in sync with the currently in use
version by the SDK (see `scripts/setup/zap.json` and
`scripts/tools/zap/zap_execution.py` for the minimum supported version).
Beyond basic zap file selection, there are also `.json` zap settings that define
additional cluster info: source XML files, sdk-access methods and data types.
There are only two such files currently in use:
- `src/app/zap-templates/zcl/zcl.json` is the **default** one
- `src/app/zap-templates/zcl/zcl-with-test-extensions.json` is used by
`all-clusters-app` to show how a cluster extension may be configured with
minimal changes from `zcl.json` (but it is different)
### Installing zap and environment variables
ZAP is generally installed as a third-party tool via CIPD during the build
environment bootstrap (see `scripts/setup/zap.json`), which makes `zap-cli`
available in `$PATH` when running in a build environment.
When matter scripts need to invoke `zap-cli` (for code generation) or `zap` (to
start the UI tool), they make use of the following environment variables to
figure out where the zap tool is located (in order of precedence):
- if `$ZAP_DEVELOPMENT_PATH` is set, code assumes you are running zap from
source. Use this if you develop zap. Zap has to be bootstrapped (generally
`npm ci` but check zap documentation for this. Some scripts have a
`--run-bootstrap` command line argument to do this for you)
- if `$ZAP_INSTALL_PATH` is set, code assumes that `zap` or `zap-cli` is
available in the given path. This is generally an unpacked release.
- otherwise, scripts will assume `zap`/`zap-cli` is in `$PATH` (this is the
case when running in a bootstrapped environment)
### Using a UI to edit `.zap` files
Generally you need to invoke zap with appropriate zcl and generate arguments.
Most of code generation is app specific, so you generally want something of the
form
`--gen src/app/zap-templates/app-templates.json --zcl $ZCL_JSON_FILE $ZAP_FILE_TO_EDIT`
Since this is tedious to type, the SDK provides a
`scripts/tools/zap/run_zaptool.sh` script to automate this:
```bash
# Ensure zap is in $PATH or set $ZAP_INSTALL_PATH or $ZAP_DEVELOPMENT_PATH
./scripts/tools/zap/run_zaptool.sh examples/lighting-app/lighting-common/lighting-app.zap
```
### Human-readable code generation inputs (`*.matter`)
`.zap` files are large json files that are generally not human readable. As a
result, the Matter SDK also keeps an equivalent `*.matter` file along side
`.zap` files that contain the same data as `.zap` files, targeted specifically
for matter:
- They are designed to be human readable, looking like a IDL (think protobuf
or android `aidl`, thrift idl etc.)
- We strive to make them contain only Matter-specific data (`.zap` files
contain more generic data and is designed to be ZigBee backwards compatible)
Currently `.matter` files are generated from `.zap` files during the application
specific codegen.
### `*.matter` parsing and codegen
`*.matter` files are both human and machine readable. Code that can process
these files is available at `scripts/py_matter_idl` and `scripts/codegen.py`.
You can read the
[scripts/py_matter_idl/matter_idl/README.md](../scripts/py_matter_idl/matter_idl/README.md)
for details of how things work.
`scripts/codegen.py` can generate various outputs based on an input `*.matter`
file.
The split between `.zap` and `.matter` currently exists as an experiment of code
generation technologies. Currently `.matter`-based Python code generation:
- has fewer third party dependencies than `zap`, which installs a significant
number of `npm` packages.
- runs significantly faster than zap
- offers more flexible code generation (can generate multiple files per
cluster for example, without which some compiles would run out of RAM on
large compilations)
- has a more flexible templating language
- has human readable (and potentially editable) input
- is more easily provable deterministic (`zap` uses an underlying sqlite
database and some legacy assumptions from zigbee have historically caused
non-determinism)
- uses a synchronous processing model which is potentially easier to develop
for
- has lower complexity, is unit tested and uses typing extensively
Ideally, the project would be to have a single code generation method in the
long term that has all the benefits and none of the drawbacks. We are not there
yet, however we likely want:
- Flexible codegen (we will need to split output by clusters or other rules)
- Human-readable inputs that enable code reviews and audits
- Rules that a script can validate based on CSA data model (ensure mandatory
attribute settings are followed, ensure proper device type adherence, ensure
correct cluster and data type definitions)
- Easy to maintain and develop for chosen languages/templates/codegen in
general
## Code generation outputs and templates
Code that is generated:
- **Application-specific**:
- ZAP generation is based on `.zap` files in `examples/` and generates
server-side processing data: what cluster callbacks to set up, what RAM
to reserve for attribute storage etc.
- `Codegen.py` will also generate a subset of application-specific files
- **Automated tests**: embedded client-side tools (`chip-tool` and
`darwin-framework-tool`) generate test-definition data. Each use their own
`examples/${TOOL}/templates/tests/templates.json` to drive what gets
generated.
- **Controller clusters** target: the file
`src/controller/data_model/controller-clusters.zap` contains a set of
cluster selections to which all applications would potentially have access.
These are generally used as `all clusters selection` and the intent is to
allow any application to access any cluster as a `client side`.
Client/controllers will codegen based on this, like **tools**, **tests**,
**java**, **python** etc.
## Running codegen
### ZAP file generation
Generating all possible code (all categories above) using zap tool can be done
via:
```bash
./scripts/tools/zap_regen_all.py
```
This can be slow (several minutes). The regen tool allows selection of only
tests so that yaml test development goes faster.
```bash
./scripts/tools/zap_regen_all.py --type tests
./scripts/tools/zap_regen_all.py --type tests --tests chip-tool
```
Additionally, individual code regeneration can be done using
`./scripts/tools/zap/generate.py`:
```bash
/scripts/tools/zap/generate.py \
examples/bridge-app/bridge-common/bridge-app.zap
```
The above will just generate a `<app>.matter` file along side the `.zap` file,
as this is the only file that requires updates for applications. You can code
generate other things by passing in the `-t/--templates` argument to
generate.py. In those cases, you may also need to specify an output directory
via `-o/--output-dir`.
#### Flow for updating an application zap file:
```
# use zap UI to edit the file (or edit zap file in any other way)
./scripts/tools/zap/run_zaptool.sh $PATH_TO_ZAP_FILE
# re-generate .matter file. Note that for .matter file generation, output
# directory is NOT used
./scripts/tools/zap/generate.py $PATH_TO_ZAP_FILE
```
### Compile-time code generation / pre-generated code
A subset of code generation (both `codegen.py` and `zap-cli`) is done at compile
time or can use pre-generated output (based on gn/cmake arguments)
Rules for how `generate.py`/`codegen.py` is invoked at compile time are defined
at:
- `src/app/chip_data_model.cmake`
- `src/app/chip_data_model.gni`
Additionally, `build/chip/esp32/esp32_codegen.cmake` adds processing support for
the 2-pass cmake builds used by the Espressif `idf.py` build system.
## Pre-generation
Code pre-generation can be used:
- when compile-time code generation is not desirable. This may be for
importing into build systems that do not have the pre-requisites to run code
generation at build time or to save the code generation time at the expense
of running code generation for every possible zap/generation type
- To check changes in generated code across versions, beyond the comparisons
of golden image tests in `scripts/py_matter_idl/matter_idl/tests`
The script to trigger code pre-generation is `scripts/codepregen.py` and
requires the pre-generation output directory as an argument
```bash
scripts/codepregen.py ${OUTPUT_DIRECTORY:-./zzz_pregenerated/}
# To generate a single output you can use `--input-glob`:
scripts/codepregen.py --input-glob "*all-clusters*" --input-glob "*controller*" ${OUTPUT_DIRECTORY:-./zzz_pregenerated/}
```
### External applications/zap files
#### Ensure you have a `.matter` file
Code generation generally will use both `.zap` or `.matter` files. If you only
have a `.zap` file, you can create the corresponding `.matter` file via:
```bash
scripts/tools/zap/generate.py ${ZAP_FILE_PATH}
```
The above will use the template `src/app/zap-templates/matter-idl.json` to
generate a `.matter` file corresponding to the input `.zap` file.
`.matter` files are designed to be human readable. It is recommended to take a
look at the generated file and see if it contains what is expected and also lint
it. If anything seems wrong, the `.zap` file should be fixed (`.matter`
represents the content of `.zap`). To lint use:
```bash
scripts/idl_lint.py ${MATTER_FILE_PATH}
```
#### Running pre-generation
If you have zap files outside the CHIP repository (i.e. not in `src` or
`examples`) you should provide the root of your application source.
```bash
scripts/codepregen.py --external-root ${PATH_TO_SOURCE_ROOT} ${OUTPUT_DIRECTORY:-./zzz_pregenerated/}
```
NOTE: `$PATH_TO_SOURCE_ROOT` should be a top-level directory containing
zap/matter files as the code pre-generation will generate files based on the
path inside the root:
- if files are `$PATH_TO_SOURCE_ROOT/some/path/foo.zap` this will generate
files into `$OUTPUT_DIRECTORY/some/path/foo/...`
### Using pre-generated code
Instead of generating code at compile time, the chip build system accepts usage
of a pre-generated folder. It assumes the structure that `codepregen.py`
creates. To invoke use:
- `build_examples.py` builds accept `--pregen-dir` as an argument, such as:
```shell
./scripts/build/build_examples.py --target $TARGET --pregen-dir $PREGEN_DIR build
```
- `gn` builds allow setting `chip_code_pre_generated_directory` as an
argument, such as:
```shell
gn gen --check --fail-on-unused-args --args='chip_code_pre_generated_directory="/some/pregen/dir"'
```
- `cmake` builds allow setting `CHIP_CODEGEN_PREGEN_DIR` variable (which will
get propagated to the underlying `gn` builds as needed), such as:
```shell
west build --cmake-only \
-d /workspace/out/nrf-nrf5340dk-light \
-b nrf5340dk_nrf5340_cpuapp \
/workspace/examples/lighting-app/nrfconnect
-- -DCHIP_CODEGEN_PREGEN_DIR=/some/pregen/dir
idf.py -C examples/all-clusters-app/esp32 \
-B /workspace/out/esp32-m5stack-all-clusters \
-DCHIP_CODEGEN_PREGEN_DIR=/some/pregen/dir \
reconfigure
cmake -S /workspace/examples/lighting-app/mbed \
-B /workspace/out/mbed-cy8cproto_062_4343w-light \
-GNinja \
-DMBED_OS_PATH=/workspace/third_party/mbed-os/repo \
-DMBED_OS_PATH=/workspace/third_party/mbed-os/repo \
-DMBED_OS_POSIX_SOCKET_PATH=/workspace/third_party/mbed-os-posix-socket/repo \
-DCHIP_CODEGEN_PREGEN_DIR=/some/pregen/dir
```
### Code generation unit testing
Code generation is assumed stable between builds and the build system aims to
detect changes in code gen using golden image tests.
#### `codegen.py` tests
These tests run against golden inputs/outputs from `scripts/idl/tests`.
`available_tests.yaml` contains the full list of expected generators and outputs
and the test is run via `test_generators.py`. Use the environment variable
`IDL_GOLDEN_REGENERATE` to force golden image replacement during running of
`ninja check`:
```shell
IDL_GOLDEN_REGENERATE=1 ninja check
```
#### `generate.py` tests
These tests run against golden inputs/outputs from `scripts/tools/zap/tests`.
`available_tests.yaml` contains the full list of expected generators and outputs
and the test is run via `scripts/tools/zap/test_generate.py`. Use the
environment variable `ZAP_GENERATE_GOLDEN_REGENERATE` to force golden image
replacement during running of `ninja check`.
```shell
ZAP_GENERATE_GOLDEN_REGENERATE=1 ninja check
```
Alternatively, the golden image can also be re-generated by running the
stand-alone test in a bootstrapped environment:
```shell
./scripts/tools/zap/test_generate.py --output out/gen --regenerate
```