Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
5416595
Update
shoumikhin Jul 31, 2026
008351d
Update
shoumikhin Jul 31, 2026
b741e2d
Update
shoumikhin Jul 31, 2026
58b61c9
Update
shoumikhin Jul 31, 2026
2e51b61
Update
shoumikhin Jul 31, 2026
058a1f6
Update
shoumikhin Jul 31, 2026
79688cb
Update
shoumikhin Jul 31, 2026
077bb9d
Update
shoumikhin Aug 1, 2026
ab9ad38
Update
shoumikhin Aug 1, 2026
060cc4b
Update
shoumikhin Aug 1, 2026
8ca7e77
Update
shoumikhin Aug 1, 2026
23d62d4
Update
shoumikhin Aug 1, 2026
c076daa
Update
shoumikhin Aug 1, 2026
179b67c
Update
shoumikhin Aug 1, 2026
e18448d
Update
shoumikhin Aug 1, 2026
596690f
Update
shoumikhin Aug 1, 2026
e33f3c1
Update
shoumikhin Aug 1, 2026
2749cc7
Update
shoumikhin Aug 2, 2026
74f27c3
Update
shoumikhin Aug 2, 2026
24ee4f9
Update
shoumikhin Aug 2, 2026
b1075f5
Update
shoumikhin Aug 2, 2026
b5d2c6b
Update
shoumikhin Aug 2, 2026
3447ef5
Update
shoumikhin Aug 2, 2026
642fa5e
Update
shoumikhin Aug 2, 2026
aebfb4d
Update
shoumikhin Aug 2, 2026
607ef93
Update
shoumikhin Aug 2, 2026
0670cb3
Update
shoumikhin Aug 2, 2026
abb743a
Update
shoumikhin Aug 2, 2026
7e257ad
Update
shoumikhin Aug 2, 2026
546ca1c
Update
shoumikhin Aug 2, 2026
16fccef
Update
shoumikhin Aug 2, 2026
8fbcb0d
Update
shoumikhin Aug 2, 2026
1555421
Update
shoumikhin Aug 2, 2026
59cf985
Update
shoumikhin Aug 2, 2026
b2ff575
Update
shoumikhin Aug 2, 2026
d41a53d
Update
shoumikhin Aug 3, 2026
e9abdd5
Update
shoumikhin Aug 3, 2026
d3ee68a
Update
shoumikhin Aug 3, 2026
8bfba13
Update
shoumikhin Aug 3, 2026
c0ebd90
Update
shoumikhin Aug 3, 2026
7e6e924
Update
shoumikhin Aug 3, 2026
d363b85
Update
shoumikhin Aug 3, 2026
2c3b441
Update
shoumikhin Aug 3, 2026
395f1bd
Update
shoumikhin Aug 3, 2026
217b18d
Update
shoumikhin Aug 3, 2026
b9eade5
Update
shoumikhin Aug 3, 2026
f5a9b97
Update
shoumikhin Aug 3, 2026
0799a55
Update
shoumikhin Aug 3, 2026
a10d1a5
Update
shoumikhin Aug 3, 2026
23d417d
Update
shoumikhin Aug 3, 2026
deb58fd
Update
shoumikhin Aug 3, 2026
012df50
Update
shoumikhin Aug 3, 2026
59bfbb4
Update
shoumikhin Aug 3, 2026
581fbf3
Update
shoumikhin Aug 3, 2026
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
175 changes: 175 additions & 0 deletions .ci/scripts/wheel/test_cpp_sdk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

"""Checks that the installed wheel is usable as a C++ SDK.

The wheel ships a prebuilt runtime library plus a CMake package config, so a
standalone application can find_package(executorch) and link
executorch::runtime without building ExecuTorch from source. These checks run
against the installed wheel only; they never look at the source tree's build
directory.

Two properties are verified:

1. Exactly one shipped library defines the backend registry. Backends register
into a process-wide table owned by the runtime, so a second definition would
silently give the process two tables and let a backend register into the one
nobody reads.
2. A C++ consumer builds and runs against the wheel, and records a dependency
on the shipped runtime with a relocatable RUNPATH.
"""

import os
import re
import shutil
import subprocess
from pathlib import Path

# Registry entry points. A second definer of any of these means a second
# process-wide registry.
_REGISTRY_SYMBOLS = (
"executorch::runtime::register_backend",
"executorch::runtime::get_num_registered_backends",
"executorch::runtime::get_backend_class",
)

# `nm -DC` prints "<hexaddr> <kind> <name>" for a definition and
# " U <name>" for an undefined reference.
_DEFINED = re.compile(r"^[0-9a-fA-F]+\s+(?P<kind>[A-Za-z])\s+(?P<name>.+)$")

# Symbol kinds that mean the object owns the code or storage.
_OWNING_KINDS = frozenset("TtBbDdGgSsRrWV")

_CONSUMER_SOURCE = """\
#include <executorch/runtime/backend/interface.h>
#include <executorch/runtime/platform/runtime.h>

#include <cstdio>

int main() {
executorch::runtime::runtime_init();
std::printf(
"registered backends: %zu\\n",
(size_t)executorch::runtime::get_num_registered_backends());
return 0;
}
"""

_CONSUMER_CMAKE = """\
cmake_minimum_required(VERSION 3.24)
project(executorch_wheel_consumer CXX)
find_package(executorch REQUIRED)
add_executable(consumer consumer.cpp)
target_link_libraries(consumer PRIVATE executorch::runtime)
"""


def _installed_package_dir() -> Path:
"""The installed executorch package, never the source checkout."""
import executorch

return Path(list(executorch.__path__)[0]).resolve()


def _shipped_shared_objects(package_dir: Path):
return [
path
for path in sorted(package_dir.rglob("*.so*"))
if path.is_file() and not path.is_symlink()
]


def _defines_symbol(library: Path, symbol: str) -> bool:
result = subprocess.run(
["nm", "-DC", str(library)], capture_output=True, text=True, check=False
)
for line in result.stdout.splitlines():
if symbol not in line:
continue
match = _DEFINED.match(line)
if (
match
and match.group("name").startswith(symbol)
and match.group("kind") in _OWNING_KINDS
):
return True
return False


def test_single_backend_registry() -> None:
"""Exactly one shipped library may define the backend registry."""
assert shutil.which("nm") is not None, "nm is required to inspect the wheel"

package_dir = _installed_package_dir()
libraries = _shipped_shared_objects(package_dir)
assert libraries, f"no shared libraries found under {package_dir}"

for symbol in _REGISTRY_SYMBOLS:
definers = [lib for lib in libraries if _defines_symbol(lib, symbol)]
pretty = [str(lib.relative_to(package_dir)) for lib in definers]
assert len(definers) == 1, (
f"expected exactly one library to define {symbol}, found "
f"{len(definers)}: {pretty}. More than one definition means the "
f"process has more than one backend registry."
)
print(f"✓ single backend registry across {len(libraries)} shipped libraries")


def test_cpp_consumer(work_dir: Path) -> None:
"""A standalone C++ app builds and runs against the installed wheel."""
assert shutil.which("cmake") is not None, "cmake is required to build a consumer"

package_dir = _installed_package_dir()
config = package_dir / "share" / "cmake" / "executorch-config.cmake"
assert config.is_file(), f"wheel is missing its CMake package config: {config}"

source_dir = work_dir / "consumer"
build_dir = work_dir / "consumer-build"
source_dir.mkdir(parents=True, exist_ok=True)
(source_dir / "consumer.cpp").write_text(_CONSUMER_SOURCE)
(source_dir / "CMakeLists.txt").write_text(_CONSUMER_CMAKE)

subprocess.run(
[
"cmake",
"-S",
str(source_dir),
"-B",
str(build_dir),
f"-DCMAKE_PREFIX_PATH={config.parent}",
],
check=True,
)
subprocess.run(["cmake", "--build", str(build_dir)], check=True)

consumer = build_dir / "consumer"
# No LD_LIBRARY_PATH: the imported target is responsible for making the
# shipped runtime findable.
environment = {
key: value for key, value in os.environ.items() if key != "LD_LIBRARY_PATH"
}
subprocess.run([str(consumer)], check=True, env=environment)
print("✓ C++ consumer builds and runs against the installed wheel")

assert shutil.which("readelf") is not None, "readelf is required to check the ELF"

dynamic = subprocess.run(
["readelf", "-d", str(consumer)], capture_output=True, text=True, check=True
).stdout
assert "libexecutorch.so" in dynamic, (
"the consumer does not depend on the shipped runtime; "
f"dynamic section was:\n{dynamic}"
)
assert "$ORIGIN" in dynamic, (
"the consumer has no $ORIGIN-relative RUNPATH, so it is not "
f"relocatable; dynamic section was:\n{dynamic}"
)
print("✓ consumer depends on the shipped runtime with a relocatable RUNPATH")


def run_tests(work_dir: Path) -> None:
test_single_backend_registry()
test_cpp_consumer(work_dir)
9 changes: 9 additions & 0 deletions .ci/scripts/wheel/test_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
# LICENSE file in the root directory of this source tree.

import platform
import tempfile
from pathlib import Path

import test_base
import test_cpp_sdk
from examples.models import Backend, Model

if __name__ == "__main__":
Expand Down Expand Up @@ -41,6 +44,12 @@

test_base.test_cmsis_nn_install()

# The wheel ships a prebuilt C++ runtime and a CMake package config, so
# check that a standalone application can actually link and run against
# them, and that the process still has a single backend registry.
with tempfile.TemporaryDirectory() as work_dir:
test_cpp_sdk.run_tests(Path(work_dir))

test_base.run_tests(
model_tests=[
test_base.ModelTest(
Expand Down
10 changes: 10 additions & 0 deletions .ci/scripts/wheel/test_linux_aarch64.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

import tempfile
from pathlib import Path

import test_base
import test_cpp_sdk
from examples.models import Backend, Model

if __name__ == "__main__":
Expand All @@ -26,6 +30,12 @@
), f"OpenvinoBackend not found in registered backends: {registered}"
print("✓ OpenvinoBackend is registered")

# The wheel ships a prebuilt C++ runtime and a CMake package config, so check
# that a standalone application can actually link and run against them, and
# that the process still has a single backend registry.
with tempfile.TemporaryDirectory() as work_dir:
test_cpp_sdk.run_tests(Path(work_dir))

test_base.run_tests(
model_tests=[
test_base.ModelTest(
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/build-wheels-aarch64-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ on:
paths:
- .ci/**/*
- .github/workflows/build-wheels-aarch64-linux.yml
- '**/CMakeLists.txt'
- examples/**/*
- pyproject.toml
- setup.py
- tools/cmake/**/*
push:
branches:
- nightly
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/build-wheels-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ on:
paths:
- .ci/**/*
- .github/workflows/build-wheels-linux.yml
- '**/CMakeLists.txt'
- examples/**/*
- pyproject.toml
- setup.py
- tools/cmake/**/*
push:
branches:
- nightly
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/build-wheels-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ on:
paths:
- .ci/**/*
- .github/workflows/build-wheels-macos.yml
- '**/CMakeLists.txt'
- examples/**/*
- pyproject.toml
- setup.py
- tools/cmake/**/*
push:
branches:
- nightly
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/build-wheels-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ on:
paths:
- .ci/**/*
- .github/workflows/build-wheels-windows.yml
- '**/CMakeLists.txt'
- examples/**/*
- pyproject.toml
- setup.py
- tools/cmake/**/*
push:
branches:
- nightly
Expand Down
Loading
Loading