Skip to content

Commit 42efed9

Browse files
Add OTA_IMAGES required for Software Update CI Jobs (project-chip#40939)
* Fix ruff and isort * Updated build paths for OTA_IMAGES * Added missing append into test_env.yaml * Fix issue * Fix rm path * Fix version assignement * Moved py script to bash * Restyled by whitespace * Restyled by shellharden * Restyled by shfmt * Fix lint issues * Restyled by whitespace * Restyled by shfmt * Removed python version. Added info to show which files where created during the script. * Restyled by shellharden * Added test to verify the version is correct * Added version 5 * Restyled by autopep8 * Removed long string and replaced with arg list. Updated to check up to 5 ota images * fix: Added missing comma that was causing two invalid parameters * Updated range to build one image * Removed harcoded paths for image build. Added range in the tests.yaml. Added paths for apps to validate in tests.yaml. Removed OTA env variables that will not be used for the moment * Ruff check * isort fix * Removed code modification to update the version, now using target_cflags. Updated paths for version creation. * Restyled by whitespace * Restyled by shellharden * Env variable name corrected. * Fix after review * Restyled by shfmt --------- Co-authored-by: Restyled.io <[email protected]>
1 parent a2f630d commit 42efed9

File tree

4 files changed

+291
-1
lines changed

4 files changed

+291
-1
lines changed

.github/workflows/tests.yaml

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,35 @@ jobs:
795795
- name: ccache stats
796796
run: ccache -s
797797

798+
- name: Build linux-x64-requestor-app
799+
env:
800+
CCACHE_DIR: "${GITHUB_WORKSPACE}/.ccache"
801+
run: >-
802+
./scripts/run_in_build_env.sh "./scripts/build/build_examples.py
803+
--target linux-x64-ota-requestor-${BUILD_VARIANT}-tsan-clang-test
804+
--pw-command-launcher=ccache build --copy-artifacts-to objdir-clone
805+
&& rm -rf out/linux-x64-ota-requestor-${BUILD_VARIANT}-tsan-clang-test"
806+
807+
- name: Build linux-x64-provider-app
808+
env:
809+
CCACHE_DIR: "${GITHUB_WORKSPACE}/.ccache"
810+
run: >-
811+
./scripts/run_in_build_env.sh "./scripts/build/build_examples.py
812+
--target linux-x64-ota-provider-${BUILD_VARIANT}-tsan-clang-test
813+
--pw-command-launcher=ccache build --copy-artifacts-to objdir-clone
814+
&& rm -rf out/linux-x64-ota-provider-${BUILD_VARIANT}-tsan-clang-test"
815+
816+
- name: Build Software Update ota images with new version.
817+
env:
818+
CCACHE_DIR: "${GITHUB_WORKSPACE}/.ccache"
819+
run: >-
820+
./scripts/run_in_build_env.sh "./scripts/build/build_ota_images.sh --out-prefix out/su_ota_images_min --max-range 2
821+
&& mv out/su_ota_images_min-v*/*.ota objdir-clone
822+
&& rm -rf out/su_ota_images_min-v*"
823+
824+
- name: ccache stats
825+
run: ccache -s
826+
798827
- name: Install push_av_server dependencies
799828
run: >-
800829
./scripts/run_in_python_env.sh out/venv \
@@ -822,19 +851,25 @@ jobs:
822851
echo "FABRIC_SYNC_APP: objdir-clone/linux-x64-fabric-sync-${BUILD_VARIANT}-clang/fabric-sync" >> /tmp/test_env.yaml
823852
echo "LIGHTING_APP_NO_UNIQUE_ID: objdir-clone/linux-x64-light-data-model-no-unique-id-${BUILD_VARIANT}-clang/chip-lighting-app" >> /tmp/test_env.yaml
824853
echo "TERMS_AND_CONDITIONS_APP: objdir-clone/linux-x64-terms-and-conditions/chip-terms-and-conditions-app" >> /tmp/test_env.yaml
854+
echo "OTA_PROVIDER_APP: objdir-clone/linux-x64-ota-provider-${BUILD_VARIANT}-tsan-clang-test/chip-ota-provider-app" >> /tmp/test_env.yaml
855+
echo "OTA_REQUESTOR_APP: objdir-clone/linux-x64-ota-requestor-${BUILD_VARIANT}-tsan-clang-test/chip-ota-requestor-app" >> /tmp/test_env.yaml
825856
echo "TRACE_APP: out/trace_data/app-{SCRIPT_BASE_NAME}" >> /tmp/test_env.yaml
826857
echo "JF_CONTROL_APP: objdir-clone/linux-x64-jf-control-app/jfc-app" >> /tmp/test_env.yaml
827858
echo "JF_ADMIN_APP: objdir-clone/linux-x64-jf-admin-app/jfa-app" >> /tmp/test_env.yaml
828859
echo "CLOSURE_APP: objdir-clone/linux-x64-closure-${BUILD_VARIANT}-tsan-clang-test-unified/closure-app" >> /tmp/test_env.yaml
829860
echo "WATER_LEAK_DETECTOR_APP: objdir-clone/linux-x64-water-leak-detector-${BUILD_VARIANT}-tsan-clang-test-unified/water-leak-detector-app" >> /tmp/test_env.yaml
830861
echo "PUSH_AV_SERVER: src/tools/push_av_server/server.py" >> /tmp/test_env.yaml
831862
863+
# SU OTA Images
864+
echo "SU_OTA_REQUESTOR_V2: objdir-clone/chip-ota-requestor-app_v2.min.ota" >> /tmp/test_env.yaml
865+
832866
# Generic trace setups after applications
833867
echo "TRACE_TEST_JSON: out/trace_data/test-{SCRIPT_BASE_NAME}" >> /tmp/test_env.yaml
834868
echo "TRACE_TEST_PERFETTO: out/trace_data/test-{SCRIPT_BASE_NAME}" >> /tmp/test_env.yaml
835869
836870
- name: Verify Testing Support
837871
run: |
872+
scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/test_testing/test_ota_images_versions.py -i objdir-clone/chip-ota-requestor-app_v2.min.ota -v 2'
838873
scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/test_testing/test_IDM_10_4.py'
839874
scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/test_testing/test_TC_ICDM_2_1_full_pics.py'
840875
scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/test_testing/test_TC_ICDM_2_1_min_pics.py'
@@ -853,7 +888,6 @@ jobs:
853888
scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/test_testing/TestSpecParsingSelection.py'
854889
scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/test_testing/TestSpecParsingSupport.py'
855890
scripts/run_in_python_env.sh out/venv 'PYTHONPATH=src/python_testing:$PYTHONPATH python3 -m unittest discover -s src/python_testing/mdns_discovery/tests -p "test_*.py"'
856-
857891
- name: Run Tests
858892
run: |
859893
mkdir -p out/trace_data

scripts/build/build_ota_images.sh

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env bash
2+
3+
#
4+
# Copyright (c) 2025 Project CHIP Authors
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
set -x
20+
CHIP_ROOT="$(dirname "$0")/../.."
21+
22+
# Default values for optional arguments
23+
VENDOR_ID="0xDEAD"
24+
PRODUCT_ID="0xBEEF"
25+
MAX_RANGE=6
26+
OUT_PREFIX="out/su_ota_images_min"
27+
BUILT_IMAGES_STACK=()
28+
29+
# Parse command line arguments
30+
while [[ "$#" -gt 0 ]]; do
31+
case "$1" in
32+
--vendor-id)
33+
VENDOR_ID="$2"
34+
shift 2
35+
;;
36+
--product-id)
37+
PRODUCT_ID="$2"
38+
shift 2
39+
;;
40+
--max-range)
41+
MAX_RANGE="$2"
42+
shift 2
43+
;;
44+
--out-prefix)
45+
OUT_PREFIX="$2"
46+
shift 2
47+
;;
48+
*)
49+
echo "Unknown option: $1"
50+
exit 1
51+
;;
52+
esac
53+
done
54+
55+
echo "Values to use for build"
56+
echo "Vendor ID: $VENDOR_ID"
57+
echo "Product ID: $PRODUCT_ID"
58+
echo "Max Range: $MAX_RANGE"
59+
echo "Output Prefix: $OUT_PREFIX"
60+
61+
BASE_OUT_PREFIX="$CHIP_ROOT/$OUT_PREFIX"
62+
echo "PREFIX $BASE_OUT_PREFIX"
63+
64+
echo "Starting building the ota images with different version number."
65+
66+
for ((i = 2; i <= "$MAX_RANGE"; i++)); do
67+
echo "Building for version $i"
68+
69+
# Use a per-version GN output dir to avoid clobbering / to enable reuse
70+
OUT_DIR="$BASE_OUT_PREFIX-v$i"
71+
mkdir -p "$OUT_DIR"
72+
73+
# Pass version via GN args (target_cflags). Quotes must be escaped so the GN arg
74+
# arrives intact. gn_build_example.sh forwards key=value args to gn gen.
75+
GN_VERSION_ARGS="target_cflags=[\"-DCHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION=$i\",\"-DCHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING=\\\"$i.0\\\"\"]"
76+
77+
# Build the image without editing source files
78+
echo "Building the requestor app (out: $OUT_DIR)"
79+
./"$CHIP_ROOT"/scripts/examples/gn_build_example.sh \
80+
"$CHIP_ROOT"/examples/ota-requestor-app/linux "$OUT_DIR" \
81+
chip_config_network_layer_ble=false is_debug=false "$GN_VERSION_ARGS" >/dev/null
82+
83+
# Strip command (create .min binary in the same OUT_DIR)
84+
if [ "$(uname -s)" = "Darwin" ]; then
85+
strip "$OUT_DIR"/chip-ota-requestor-app -o "$OUT_DIR"/chip-ota-requestor-app.min >/dev/null 2>&1
86+
else
87+
strip --strip-all "$OUT_DIR"/chip-ota-requestor-app -o "$OUT_DIR"/chip-ota-requestor-app.min >/dev/null 2>&1
88+
fi
89+
90+
# Create ota image (store images under the shared OUT_PREFIX directory)
91+
OTA_IMAGE_PATH="$OUT_DIR/chip-ota-requestor-app_v$i.min.ota"
92+
python3 "$CHIP_ROOT"/src/app/ota_image_tool.py create -v "$VENDOR_ID" -p "$PRODUCT_ID" -vn "$i" -vs "$i.0" -da sha256 "$OUT_DIR"/chip-ota-requestor-app.min "$OTA_IMAGE_PATH"
93+
94+
BUILT_IMAGES_STACK+=("$OTA_IMAGE_PATH")
95+
done
96+
97+
echo "Generated files"
98+
for item in "${BUILT_IMAGES_STACK[@]}"; do
99+
echo "$item"
100+
done
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env -S python3 -B
2+
#
3+
# Copyright (c) 2025 Project CHIP Authors
4+
# All rights reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
import logging
20+
import os
21+
import subprocess
22+
import sys
23+
24+
import click
25+
26+
CHIP_ROOT = os.path.abspath(os.path.join(
27+
os.path.dirname(__file__), '../../..'))
28+
RUNNER_SCRIPT_DIR = os.path.join(CHIP_ROOT, 'scripts/tests')
29+
OTA_TOOL_DIR = os.path.join(CHIP_ROOT, 'src/app')
30+
31+
32+
def run_single_test(otaimage: str, otaimage_version: int) -> int:
33+
34+
# Find the image
35+
ota_image = os.path.join(CHIP_ROOT, otaimage)
36+
37+
ota_image_bin = os.path.join(CHIP_ROOT, f'{ota_image}.bin')
38+
39+
# Extract the image into the directory
40+
ota_tool = os.path.abspath(os.path.join(
41+
OTA_TOOL_DIR, 'ota_image_tool.py'))
42+
extract_cmd = str(ota_tool) + ' extract ' + str(ota_image) + ' ' + str(ota_image_bin)
43+
status = subprocess.call(extract_cmd, shell=True)
44+
logging.info("Extract image : " + str(status))
45+
if status != 0:
46+
logging.error(f"Failed to extract the image from {ota_image}")
47+
exit(1)
48+
logging.info(f"Image extracted into {ota_image_bin}")
49+
50+
# Mod update
51+
cmd_mod = "chmod +x " + str(ota_image_bin)
52+
status = subprocess.call(cmd_mod, shell=True)
53+
if status != 0:
54+
logging.info(f"Failed to change +x permission on the image {ota_image_bin}")
55+
exit(1)
56+
57+
app_args = ' --discriminator 1234 '
58+
59+
script_args = [
60+
"--commissioning-method on-network",
61+
"--passcode 20202021",
62+
"--discriminator 1234",
63+
f"--int-arg SOFTWAREVERSION:{otaimage_version}",
64+
"--storage-path admin_storage.json"
65+
]
66+
67+
script_args = " ".join(script_args)
68+
69+
script = os.path.abspath(os.path.join(
70+
CHIP_ROOT, 'src/python_testing/test_testing/test_ota_version.py'))
71+
72+
# run_python_test uses click so call as a command
73+
run_python_test = os.path.abspath(os.path.join(
74+
RUNNER_SCRIPT_DIR, 'run_python_test.py'))
75+
test_cmd = str(run_python_test) + ' --factory-reset --app ' + str(ota_image_bin) + ' --app-args "' + \
76+
app_args + '" --script ' + str(script) + ' --script-args "' + script_args + '"'
77+
78+
process_status = subprocess.call(test_cmd, shell=True)
79+
80+
cmd_clean = "rm " + str(ota_image_bin)
81+
subprocess.call(cmd_clean, shell=True)
82+
return process_status
83+
84+
85+
@click.command()
86+
@click.option('--otaimages', '-i', multiple=True, type=click.Path(exists=True))
87+
@click.option('--otaimagesversions', '-v', multiple=True, type=(int))
88+
def main(otaimages: str, otaimagesversions: int):
89+
if len(otaimages) == 0 or otaimagesversions == 0:
90+
logging.error("Must provide at least one image to verify")
91+
exit(1)
92+
if len(otaimages) != len(otaimagesversions):
93+
logging.error("Provided non matching images to provided versions")
94+
passes = []
95+
main_status = 0
96+
for index in range(len(otaimages)):
97+
otaimage = otaimages[index]
98+
otaimage_version = otaimagesversions[index]
99+
logging.info(f"Verifying the image {otaimage} has the reported version {otaimage_version}")
100+
status = run_single_test(otaimage=otaimage, otaimage_version=otaimage_version)
101+
passes.append((otaimage, otaimage_version, status))
102+
103+
for iter in passes:
104+
if iter[2] != 0:
105+
logging.error(f"Image version missmatched for ota image: {iter[0]} expected: {iter[1]}")
106+
main_status = 1
107+
108+
sys.exit(main_status)
109+
110+
111+
if __name__ == '__main__':
112+
main()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#
2+
# Copyright (c) 2025 Project CHIP Authors
3+
# All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
import logging
18+
19+
from mobly import asserts
20+
21+
import matter.clusters as Clusters
22+
from matter.testing.matter_testing import MatterBaseTest, async_test_body, default_matter_test_main
23+
24+
25+
class TestCheckSoftwareVersion(MatterBaseTest):
26+
27+
@async_test_body
28+
async def setup_class(self):
29+
super().setup_class()
30+
logging.info("This log has started")
31+
32+
@async_test_body
33+
async def test_ota_image_version(self):
34+
self.print_step(0, "Commissioning - already done")
35+
expected_software_version = self.matter_test_config.global_test_params['SOFTWAREVERSION']
36+
37+
self.print_step(1, f"Verify cluster version is: {expected_software_version}")
38+
current_software_version = await self.read_single_attribute_check_success(cluster=Clusters.BasicInformation, attribute=Clusters.BasicInformation.Attributes.SoftwareVersion)
39+
asserts.assert_equal(current_software_version, expected_software_version,
40+
f"Version {current_software_version} is not the expected {expected_software_version}")
41+
42+
43+
if __name__ == '__main__':
44+
default_matter_test_main()

0 commit comments

Comments
 (0)