Skip to content

Commit b094b0e

Browse files
zingoCopilot
andauthored
Zephyr: Update to Zephyr 4.4 (#19807)
Update the Zephyr module setup and CI flow for Zephyr 4.4. Run Zephyr tests in a Python 3.12 venv, update README-tested setup commands, and keep the Zephyr SDK version and release asset handling in one shared helper. Pre-cache Zephyr SDK release assets in the CI Docker image and use the cached assets through the Zephyr SDK release proxy during test setup. Also clear the CI Docker image's global LD_LIBRARY_PATH before running Zephyr tests so SDK host tools use their intended runtime libraries. Update the Arm/Zephyr sample documentation and board configuration for the current Alif Ensemble E8 RTSS-HE/RTSS-HP target names, memory settings, and expected output. Fix size_test direct link with SDK 1.0.1 The Zephyr preset size test builds test/size_test.cpp directly with arm-zephyr-eabi, outside Zephyr's normal application link flow. With Zephyr SDK 1.0.1 this link no longer gets nosys-style syscall stubs or a permissive linker script. FileDataLoader and libc therefore pull in symbols such as open, read, close, fstat, gettimeofday, _exit, and Picolibc heap symbols, causing the size-test link to fail. Add size-test-only no-OS syscall stubs behind EXECUTORCH_SIZE_TEST_NO_OS_LINK and use a small generic linker script for this direct Zephyr SDK link. The option is enabled only for the zephyr-preset size-test CI path, so normal Zephyr app builds are unchanged. ### Test plan Mosted tested by Github CI , Alif readme tested by hand. Signed-off-by: Zingo Andersen <Zingo.Andersen@arm.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent eb7473b commit b094b0e

24 files changed

Lines changed: 1067 additions & 178 deletions

.ci/docker/common/install_zephyr.sh

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
#!/bin/bash
32
# Copyright (c) Meta Platforms, Inc. and affiliates.
43
# All rights reserved.
@@ -9,27 +8,27 @@
98

109
set -ex
1110

11+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12+
1213
# shellcheck source=/dev/null
13-
source "$(dirname "${BASH_SOURCE[0]}")/utils.sh"
14+
source "${SCRIPT_DIR}/utils.sh"
1415

15-
# Double check if the NDK version is set
16+
# Double check if Zephyr setup is enabled.
1617
[ -n "${ZEPHYR_SDK}" ]
1718

18-
install_prerequiresites() {
19+
install_prerequisites() {
1920
rm /var/lib/dpkg/info/libc-bin.*
2021
apt-get clean
2122
apt-get -y update
2223
apt-get install -y libc-bin
2324
apt-get -y update
2425
apt-get clean
25-
apt-get install --no-install-recommends -y dos2unix
2626
apt-get install --no-install-recommends -y ca-certificates
2727
apt-get install -y --reinstall libc-bin
2828
apt-get install --no-install-recommends -y file
2929
apt-get install --no-install-recommends -y locales
3030
apt-get install --no-install-recommends -y git
3131
apt-get install --no-install-recommends -y build-essential
32-
apt-get install --no-install-recommends -y cmake
3332
apt-get install --no-install-recommends -y ninja-build gperf
3433
apt-get install --no-install-recommends -y device-tree-compiler
3534
apt-get install --no-install-recommends -y wget
@@ -49,16 +48,14 @@ install_prerequiresites() {
4948
apt install -y python3.12 python3.12-dev python3.12-venv python3-pip
5049
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1
5150

52-
# Upgrade cmake ot 3.24
53-
apt update
54-
apt install cmake
55-
apt install software-properties-common lsb-release
51+
# Upgrade cmake to 3.24 or newer.
52+
apt install -y software-properties-common lsb-release
5653
apt update
5754
test -f /usr/share/doc/kitware-archive-keyring/copyright || \
5855
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
59-
"deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/kitware.list > /dev/null
56+
echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/kitware.list > /dev/null
6057
apt update
61-
apt install cmake
58+
apt install -y cmake
6259

6360
# Install additional required software for Zephyr
6461
apt install --no-install-recommends -y ccache \
@@ -77,19 +74,19 @@ install_prerequiresites() {
7774
apt-get clean -y
7875
apt-get autoremove --purge -y
7976
rm -rf /var/lib/apt/lists/*
80-
wget https://apt.kitware.com/kitware-archive.sh && \
81-
chmod +x kitware-archive.sh && \
82-
./kitware-archive.sh && \
83-
rm -f kitware-archive.sh
8477
pip_install --no-cache-dir west
8578
pip_install pyelftools
8679

87-
# Zephyr SDK
88-
wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.17.4/zephyr-sdk-0.17.4_linux-x86_64.tar.xz
89-
tar -xf zephyr-sdk-0.17.4_linux-x86_64.tar.xz
90-
rm -f zephyr-sdk-0.17.4_linux-x86_64.tar.xz*
91-
# Save setup to later and this get symlinked in to another folder in the test in trunk.yml
92-
#./zephyr-sdk-0.17.4/setup.sh -c -t arm-zephyr-eabi
80+
# Cache the Zephyr SDK release assets in the image. CI still runs
81+
# `west sdk install` in its workspace, but the local proxy fallback can serve
82+
# these files without downloading them on every job.
83+
local zephyr_sdk_version
84+
zephyr_sdk_version="$(python3 "${SCRIPT_DIR}/zephyr_sdk_release_proxy.py" --print-version)"
85+
local zephyr_sdk_cache_dir="${ZEPHYR_SDK_RELEASE_PROXY_CACHE_DIR:-/opt/zephyr-sdk-cache/v${zephyr_sdk_version}}"
86+
python3 "${SCRIPT_DIR}/zephyr_sdk_release_proxy.py" \
87+
--version "${zephyr_sdk_version}" \
88+
--cache-dir "${zephyr_sdk_cache_dir}" \
89+
--populate-cache
9390
}
9491

95-
install_prerequiresites
92+
install_prerequisites
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2026 Arm Limited and/or its affiliates.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
"""Local Zephyr SDK release proxy for `west sdk install`.
8+
9+
This avoids GitHub releases API rate limits by serving a tiny synthetic releases
10+
API locally. Asset requests are served from a cache directory and populated with
11+
`wget` on cache miss.
12+
13+
Examples:
14+
15+
# Prepopulate CI cache only.
16+
.ci/docker/common/zephyr_sdk_release_proxy.py \
17+
--version <version> \
18+
--cache-dir .cache/zephyr-sdk/v<version> \
19+
--populate-cache
20+
21+
# Serve cached assets and release metadata.
22+
.ci/docker/common/zephyr_sdk_release_proxy.py \
23+
--version <version> \
24+
--cache-dir .cache/zephyr-sdk/v<version> \
25+
--port 8765
26+
27+
west sdk install \
28+
--version <version> \
29+
--api-url http://127.0.0.1:8765/releases \
30+
--gnu-toolchains arm-zephyr-eabi
31+
"""
32+
33+
from __future__ import annotations
34+
35+
import argparse
36+
import hashlib
37+
import http.server
38+
import json
39+
import os
40+
import platform
41+
import re
42+
import subprocess
43+
import sys
44+
import urllib.parse
45+
from pathlib import Path
46+
47+
48+
DEFAULT_SDK_VERSION = "1.0.1"
49+
50+
51+
class AssetVerificationError(Exception):
52+
pass
53+
54+
55+
def default_sdk_version() -> str:
56+
return (
57+
os.environ.get("ZEPHYR_SDK_VERSION")
58+
or os.environ.get("SDK_VERSION")
59+
or DEFAULT_SDK_VERSION
60+
)
61+
62+
63+
def host_tuple() -> tuple[str, str]:
64+
system = platform.system()
65+
machine = platform.machine()
66+
67+
if system != "Linux":
68+
raise SystemExit(f"Unsupported host OS: {system}")
69+
70+
if machine in ("x86_64", "AMD64"):
71+
return "linux", "x86_64"
72+
if machine in ("aarch64", "arm64"):
73+
return "linux", "aarch64"
74+
75+
raise SystemExit(f"Unsupported host architecture: {machine}")
76+
77+
78+
def release_base_url(version: str) -> str:
79+
return f"https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v{version}"
80+
81+
82+
def asset_names(version: str, toolchain: str) -> list[str]:
83+
host_os, host_arch = host_tuple()
84+
return [
85+
"sha256.sum",
86+
f"zephyr-sdk-{version}_{host_os}-{host_arch}_minimal.tar.xz",
87+
f"hosttools_{host_os}-{host_arch}.tar.xz",
88+
f"toolchain_gnu_{host_os}-{host_arch}_{toolchain}.tar.xz",
89+
]
90+
91+
92+
def run_wget(url: str, output: Path) -> None:
93+
output.parent.mkdir(parents=True, exist_ok=True)
94+
partial = output.with_suffix(output.suffix + ".tmp")
95+
cmd = [
96+
"wget",
97+
"--continue",
98+
"--progress=bar:force:noscroll",
99+
"--output-document",
100+
str(partial),
101+
url,
102+
]
103+
subprocess.run(cmd, check=True)
104+
partial.replace(output)
105+
106+
107+
def ensure_asset(cache_dir: Path, version: str, filename: str) -> Path:
108+
path = cache_dir / filename
109+
if path.exists():
110+
return path
111+
112+
url = f"{release_base_url(version)}/{filename}"
113+
print(f"Downloading {url}", flush=True)
114+
run_wget(url, path)
115+
return path
116+
117+
118+
def parse_sha256_sum(path: Path) -> dict[str, str]:
119+
checksums: dict[str, str] = {}
120+
for line in path.read_text().splitlines():
121+
match = re.match(r"^([0-9a-fA-F]{64})\s+(.+)$", line.strip())
122+
if match:
123+
checksums[match.group(2)] = match.group(1).lower()
124+
return checksums
125+
126+
127+
def verify_asset(cache_dir: Path, checksums: dict[str, str], filename: str) -> None:
128+
if filename == "sha256.sum":
129+
return
130+
131+
expected = checksums.get(filename)
132+
if expected is None:
133+
raise AssetVerificationError(f"No checksum entry for {filename}")
134+
135+
hasher = hashlib.sha256()
136+
with (cache_dir / filename).open("rb") as f:
137+
for chunk in iter(lambda: f.read(1024 * 1024), b""):
138+
hasher.update(chunk)
139+
digest = hasher.hexdigest()
140+
141+
if digest != expected:
142+
raise AssetVerificationError(
143+
f"Checksum mismatch for {filename}: expected {expected}, got {digest}"
144+
)
145+
146+
147+
def ensure_verified_asset(
148+
cache_dir: Path, version: str, checksums: dict[str, str], filename: str
149+
) -> Path:
150+
path = ensure_asset(cache_dir, version, filename)
151+
try:
152+
verify_asset(cache_dir, checksums, filename)
153+
except AssetVerificationError:
154+
path.unlink(missing_ok=True)
155+
path = ensure_asset(cache_dir, version, filename)
156+
verify_asset(cache_dir, checksums, filename)
157+
return path
158+
159+
160+
def populate_cache(cache_dir: Path, version: str, toolchain: str) -> None:
161+
cache_dir.mkdir(parents=True, exist_ok=True)
162+
names = asset_names(version, toolchain)
163+
164+
sha_file = ensure_asset(cache_dir, version, "sha256.sum")
165+
checksums = parse_sha256_sum(sha_file)
166+
167+
for name in names:
168+
ensure_verified_asset(cache_dir, version, checksums, name)
169+
170+
171+
def release_json(version: str, toolchain: str, base_url: str) -> bytes:
172+
assets = [
173+
{
174+
"name": name,
175+
"browser_download_url": f"{base_url}/assets/{name}",
176+
}
177+
for name in asset_names(version, toolchain)
178+
]
179+
return json.dumps([{"tag_name": f"v{version}", "assets": assets}]).encode()
180+
181+
182+
class SdkProxyHandler(http.server.SimpleHTTPRequestHandler):
183+
version: str
184+
toolchain: str
185+
cache_dir: Path
186+
187+
def log_message(self, fmt: str, *args: object) -> None:
188+
print(f"{self.address_string()} - {fmt % args}", file=sys.stderr)
189+
190+
def send_bytes(self, data: bytes, content_type: str) -> None:
191+
self.send_response(200)
192+
self.send_header("Content-Type", content_type)
193+
self.send_header("Content-Length", str(len(data)))
194+
self.end_headers()
195+
self.wfile.write(data)
196+
197+
def do_GET(self) -> None:
198+
parsed = urllib.parse.urlparse(self.path)
199+
200+
if parsed.path == "/releases":
201+
query = urllib.parse.parse_qs(parsed.query)
202+
page = query.get("page", ["1"])[0]
203+
if page == "1":
204+
host = self.headers.get("Host", "127.0.0.1")
205+
data = release_json(self.version, self.toolchain, f"http://{host}")
206+
else:
207+
data = b"[]"
208+
self.send_bytes(data, "application/json")
209+
return
210+
211+
if parsed.path.startswith("/assets/"):
212+
filename = Path(urllib.parse.unquote(parsed.path[len("/assets/") :])).name
213+
allowed = set(asset_names(self.version, self.toolchain))
214+
if filename not in allowed:
215+
self.send_error(404, f"Unknown asset: {filename}")
216+
return
217+
218+
try:
219+
if filename != "sha256.sum":
220+
sha_file = ensure_asset(self.cache_dir, self.version, "sha256.sum")
221+
path = ensure_verified_asset(
222+
self.cache_dir,
223+
self.version,
224+
parse_sha256_sum(sha_file),
225+
filename,
226+
)
227+
else:
228+
path = ensure_asset(self.cache_dir, self.version, filename)
229+
except (
230+
AssetVerificationError,
231+
subprocess.CalledProcessError,
232+
OSError,
233+
) as error:
234+
self.send_error(500, str(error))
235+
return
236+
237+
self.send_response(200)
238+
self.send_header("Content-Type", "application/octet-stream")
239+
self.send_header("Content-Length", str(path.stat().st_size))
240+
self.end_headers()
241+
with path.open("rb") as f:
242+
while chunk := f.read(1024 * 1024):
243+
self.wfile.write(chunk)
244+
return
245+
246+
self.send_error(404, "Not found")
247+
248+
249+
def default_cache_dir(version: str) -> Path:
250+
root = os.environ.get("XDG_CACHE_HOME")
251+
if root:
252+
return Path(root) / "zephyr-sdk" / f"v{version}"
253+
return Path.home() / ".cache" / "zephyr-sdk" / f"v{version}"
254+
255+
256+
def parse_args() -> argparse.Namespace:
257+
parser = argparse.ArgumentParser(description=__doc__)
258+
parser.add_argument("--version", default=default_sdk_version())
259+
parser.add_argument("--toolchain", default="arm-zephyr-eabi")
260+
parser.add_argument("--cache-dir", type=Path)
261+
parser.add_argument("--host", default="127.0.0.1")
262+
parser.add_argument("--port", type=int, default=8765)
263+
parser.add_argument(
264+
"--print-version",
265+
action="store_true",
266+
help="Print the effective default SDK version and exit.",
267+
)
268+
parser.add_argument(
269+
"--download-only",
270+
"--populate-cache",
271+
dest="download_only",
272+
action="store_true",
273+
help="Download and verify release assets, then exit without serving.",
274+
)
275+
return parser.parse_args()
276+
277+
278+
def main() -> None:
279+
args = parse_args()
280+
281+
if args.print_version:
282+
print(args.version)
283+
return
284+
285+
cache_dir = args.cache_dir or default_cache_dir(args.version)
286+
287+
if args.download_only:
288+
try:
289+
populate_cache(cache_dir, args.version, args.toolchain)
290+
except AssetVerificationError as error:
291+
raise SystemExit(str(error)) from error
292+
print(f"Cached Zephyr SDK assets in {cache_dir}")
293+
return
294+
295+
SdkProxyHandler.version = args.version
296+
SdkProxyHandler.toolchain = args.toolchain
297+
SdkProxyHandler.cache_dir = cache_dir
298+
299+
server = http.server.ThreadingHTTPServer((args.host, args.port), SdkProxyHandler)
300+
print(
301+
f"Serving Zephyr SDK release metadata at http://{args.host}:{args.port}/releases",
302+
flush=True,
303+
)
304+
print(f"Cache directory: {cache_dir}", flush=True)
305+
server.serve_forever()
306+
307+
308+
if __name__ == "__main__":
309+
main()

0 commit comments

Comments
 (0)