# Manifest File

A `.build-test-rules.yml` file is the manifest file to control whether the app will be built or tested under the rules.

One typical manifest file look like this:

```yaml
[folder]:
  enable:
    - if: [if clause]
      temporary: true  # optional, default to false. `reason` is required if `temporary` is true
      reason: [your reason]  # optional
    - ...
  disable:
    - if: [if clause]
    - ...
  disable_test:
    - if: [if clause]
    - ...
```

## Terms

### Supported Targets

This refers to the targets that are fully supported by the ESP-IDF project. You may check the supported targets by running `idf.py --list-targets`.

`idf-build-apps` will get this information dynamically from your `$IDF_PATH`. For ESP-IDF release 5.3, the supported targets are:

- esp32
- esp32s2
- esp32c3
- esp32s3
- esp32c2
- esp32c6
- esp32h2
- esp32p4

### Preview Targets

This refers to the targets that are still in preview status. You may check the preview targets by running `idf.py --list-targets --preview`.

`idf-build-apps` will get this information dynamically from your `$IDF_PATH`. For ESP-IDF release 5.3, the preview targets are:

- linux
- esp32c5
- esp32c61

## `if` Clauses

### Operands

- Capitalized Words
  - Variables defined in `IDF_PATH/components/soc/[TARGET]/include/soc/*_caps.h` or in `IDF_PATH/components/esp_rom/[TARGET]/*_caps.h`. e.g., `SOC_WIFI_SUPPORTED`, `ESP_ROM_HAS_SPI_FLASH`
  - `IDF_TARGET`
  - `IDF_VERSION` (IDF_VERSION_MAJOR.IDF_VERSION_MINOR.IDF_VERSION_PATCH. e.g., 5.2.0. Will convert to Version object to do a version comparison instead of a string comparison)
  - `IDF_VERSION_MAJOR`
  - `IDF_VERSION_MINOR`
  - `IDF_VERSION_PATCH`
  - `INCLUDE_DEFAULT` (The default value of supported targets is 1, and the default value of preview targets is 0)
  - `CONFIG_NAME` (config name defined in [](project:#config-rules))
  - environment variables, default to `0` if not set
- String, must be double-quoted. e.g., `"esp32"`, `"12345"`
- Integer, support decimal and hex. e.g., `1`, `0xAB`
- List of strings or integers, or both types at the same time. e.g., `["esp32", 1]`

### Operators

- `==`, `!=`, `>`, `>=`, `<`, `<=`
- `and`, `or`
- `in`, `not in` with list
- parentheses

### Limitations

All operators are binary operators. For more than two operands, you may use the nested parentheses trick. For example:

- `A == 1 or (B == 2 and C in [1,2,3])`
- `(A == 1 and B == 2) or (C not in ["3", "4", 5])`

## Enable/Disable Rules

By default, we enable build and test for all supported targets. In other words, all preview targets are disabled.

To simplify the manifest file, if an app needs to be build and tested on all supported targets, it does not need to be added in a manifest file. The manifest files are files that set the violation rules for apps.

Three rules (disable rules are calculated after the `enable` rule):
- `enable`: run CI build/test jobs for targets that match any of the specified conditions only
- `disable`: will not run CI build/test jobs for targets that match any of the specified conditions
- `disable_test`: will not run CI test jobs for targets that match any of the specified conditions

Each key is a folder. The rule will recursively apply to all apps inside.

### Overrides Rules

If one sub folder is in a special case, you can overwrite the rules for this folder by adding another entry for this folder itself. Each folder's rules are standalone, and will not inherit its parent's rules. (YAML inheritance is too complicated for reading)

For example, in the following code block, only `disable` rule exists in `examples/foo/bar`. It's unaware of its parent's `enable` rule.

```yaml
examples/foo:
  enable:
    - if: IDF_TARGET == "esp32"

examples/foo/bar:
  disable:
    - if: IDF_TARGET == "esp32s2"
```

## Practical Example

Here's a practical example:

```yaml
examples/foo:
  enable:
    - if IDF_TARGET in ["esp32", 1, 2, 3]
    - if IDF_TARGET not in ["4", "5", 6]
  # should be run under all targets!

examples/bluetooth:
  disable:  # disable both build and tests jobs
    - if: SOC_BT_SUPPORTED != 1
    # reason is optional if there's no `temporary: true`
  disable_test:
    - if: IDF_TARGET == "esp32"
      temporary: true
      reason: lack of ci runners  # required when `temporary: true`

examples/bluetooth/test_foo:
  # each folder's settings are standalone
  disable:
    - if: IDF_TARGET == "esp32s2"
      temporary: true
      reason: no idea
  # unlike examples/bluetooth, the apps under this folder would not be build nor test for "no idea" under target esp32s2

examples/get-started/hello_world:
  enable:
    - if: IDF_TARGET == "linux"
      reason: this one only supports linux!

examples/get-started/blink:
  enable:
    - if: INCLUDE_DEFAULT == 1 or IDF_TARGET == "linux"
      reason: This one supports all supported targets and linux
```

## Building Apps Only on Related Changes

In large projects or monorepos, it is often desirable to only run builds and tests which are somehow related to the changes in a pull request.

idf-build-apps supports this by checking whether a particular app has been modified, or depends on modified components or modified files. This check is based on the knowledge of two things: the list of components/files the app depends on, and the list of components/app which are modified in the pull request.

### Specify the List of Modified Files and Components

To enable this feature, you need to pass the list of modified files or modified components to idf-build-apps using the following CLI options:

- `--modified-files`
- `--modified-components`

For example, if the project uses Git, you can obtain the list of files modified in a branch or a pull request by calling

```shell
git diff --name-only ${pr_branch_head} $(git merge-base ${pr_branch_head} main)
```

where `pr_branch_head` is the branch of the pull request, and `main` is the default branch.

### Specifying the app dependencies

idf-build-apps uses the following rules to determine whether to build an app or not:

1. The app is built if any files in the app itself are modified (.md files are excluded)
2. If `depends_components` or `depends_filepatterns` are specified in the manifest file, idf-build-apps matches `--modified-components` and `--modified-files` against these two entries. If any of the modified components are in the `depends_components` list or any of the modified files are matched by `depends_filepatterns`, the app is built.
3. If `depends_components` or `depends_filepatterns` are not specified in the manifest files, idf-build-apps determines the list of components the app depends on using `BUILD_COMPONENTS` property in IDF build system. For the given app, this property contains the list of all the components included into the build. idf-build-apps runs `idf.py reconfigure` to determine the value of this property for the app. If any of the modified components are present in the `BUILD_COMPONENTS` list, the app is built.

For example, this is an app `example/foo`, which depends on `comp1`, `comp2`, `comp3` and all files under `common_header_files`:

```yaml
examples/foo:
  depends_components:
    - comp1
    - comp2
    - comp3
  depends_filepatterns:
    - "common_header_files/**/*"
```

This app will be built with the following CLI options:

- `--modified-files examples/foo/main/foo.c`
- `--modified-components comp1`
- `--modified-components comp2;comp4 --modified-files /tmp/foo.h`
- `--modified-files common_header_files/foo.h`
- `--modified-components comp4 --modified-files common_header_files/foo.h`

This app will not be built with the following CLI options:

- `--modified-files examples/foo/main/foo.md`
- `--modified-components bar`

### Handle Low-level Dependencies

Low-level dependencies, are components or files that are used by many others. For example, component `freertos` provides the operating system support for all apps, and ESP-IDF build system related cmake files are also used by all apps. When these items are modified, we definitely need to build and test all the apps.

To disable the build-apps-only-on-related-changes feature, you can use the CLI option `--ignore-app-dependencies-filepatterns`. Once any of the modified files matches the specified patterns, the special rules will be disabled. All apps will be built, no exceptions.

## Enhanced YAML Syntax

### Switch-Like Clauses

The Switch-Like clauses are supported by two keywords in the YAML file: `depends_components` and `depends_filepatterns`.

#### Operands

Switch cases have two main components: the `if` clause and the `default` clause. Just like a switch statement in c language, The first matched `if` clause will be applied. If no `if` clause matched, the `default` clause will be applied. Here's an example:

```yaml
test1:
  depends_components:
    - if: IDF_VERSION == "{IDF_VERSION}"
      content: [ "component_1" ]
    - if: CONFIG_NAME == "AWESOME_CONFIG"
      content: [ "component_2" ]
    - default: [ "component_3", "component_4" ]
```

`default` clause is optional. If you don't specify any `default` clause, it will return an empty array.

#### Limitations

You cannot combine a list and a switch in one node.

### Reuse Lists

To reuse the items defined in a list, you can use the `+` and `-` postfixes respectively. The order of calculation is always `+` first, followed by `-`.

#### Array Elements as Strings

The following YAML code demonstrates how to reuse the elements from a list of strings:

```yaml
.base_depends_components: &base-depends-components
  depends_components:
    - esp_hw_support
    - esp_rom
    - esp_wifi

examples/wifi/coexist:
  <<: *base-depends-components
  depends_components+:
    - esp_coex
  depends_components-:
    - esp_rom
```

After interpretation, the resulting YAML will be:

```yaml
examples/wifi/coexist:
  depends_components:
    - esp_hw_support
    - esp_wifi
    - esp_coex
```

This means that the `esp_rom` element is removed, and the `esp_coex` element is added to the `depends_components` list.

#### Array Elements as Dictionaries

In addition to reuse elements from a list of strings, you can also perform these operations on a list of dictionaries. The matching is done based on the `if` key. Here's an example:

```yaml
.base: &base
  enable:
    - if: IDF_VERSION == "5.2.0"
    - if: IDF_VERSION == "5.3.0"

foo:
  <<: *base
  enable+:
    # this if statement dictionary will override the one defined in `&base`
    - if: IDF_VERSION == "5.2.0"
      temp: true
    - if: IDF_VERSION == "5.4.0"
      reason: bar
```

After interpretation, the resulting YAML will be:

```yaml
foo:
  enable:
  - if: IDF_VERSION == "5.3.0"
  - if: IDF_VERSION == "5.2.0"
    temp: true
  - if: IDF_VERSION == "5.4.0"
    reason: bar
```

In this case, the `enable` list is extended with the new `if` statement and `reason` dictionary.
It's important to note that the `if` dictionary defined in the `+` postfix will override the earlier one when the `if` statement matches.

This demonstrates how you can use the `+` and `-` postfixes to extend and remove elements from both string and dictionary lists in our YAML.
