fix(asio): Fix asio dependency issue

sock_utils is a private dependency, but is included in a public header
Fix: guard the inclusion to only internal ASIO_SOURCE

Closes https://github.com/espressif/esp-protocols/issues/1044
This commit is contained in:
David Cermak
2026-04-17 14:12:01 +02:00
parent ec1d0e4056
commit 64a587505e
11 changed files with 117 additions and 3 deletions
@@ -15,11 +15,17 @@ jobs:
matrix:
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
idf_target: ["esp32", "esp32s2"]
example: ["asio_chat", "async_request", "socks4", "ssl_client_server", "tcp_echo_server", "udp_echo_server", "unit"]
example: ["asio_chat", "async_request", "socks4", "ssl_client_server", "tcp_echo_server", "udp_echo_server", "unit", "build_test"]
exclude:
# build_test (regression test for PR 2403) runs only on latest, v6.0 and v5.5
- { idf_ver: "release-v5.1", example: "build_test" }
- { idf_ver: "release-v5.2", example: "build_test" }
- { idf_ver: "release-v5.3", example: "build_test" }
- { idf_ver: "release-v5.4", example: "build_test" }
runs-on: ubuntu-22.04
container: espressif/idf:${{ matrix.idf_ver }}
env:
APP_DIR: ${{ matrix.example == 'unit' && 'components/asio/tests/unit' || format('components/asio/examples/{0}', matrix.example) }}
APP_DIR: ${{ (matrix.example == 'unit' || matrix.example == 'build_test') && format('components/asio/tests/{0}', matrix.example) || format('components/asio/examples/{0}', matrix.example) }}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v4
@@ -1,12 +1,21 @@
//
// SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
// SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
//
// SPDX-License-Identifier: BSL-1.0
//
#pragma once
#include "sys/socket.h"
// sock_utils' socketpair.h only declares the ::socketpair()/::pipe() shims
// that asio's own TU (asio.cpp) calls from socket_ops.ipp. Client code that
// just includes asio.hpp never needs these declarations (asio uses separate
// compilation, so client TUs only see the asio::detail::socket_ops::socketpair
// wrapper declaration, not the POSIX call). Gate the include on ASIO_SOURCE
// -- defined by asio/impl/src.hpp before including this header -- so sock_utils
// stays a private dependency of the asio component.
#if defined(ASIO_SOURCE)
#include "socketpair.h"
#endif
#include "asio_stub.hpp"
#include_next "asio/detail/config.hpp"
@@ -0,0 +1,8 @@
# Verifies that a non-main component can consume asio's public headers
# (which transitively include "socketpair.h" from sock_utils) by only
# declaring `REQUIRES asio`.
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(asio_build_test)
@@ -0,0 +1,23 @@
# asio build-only regression test
This test reproduces the follwoing build issue:
> `asio.hpp` includes `sock_utils`' `socketpair.h`, but `components/asio/CMakeLists.txt`
> used `PRIV_REQUIRES sock_utils`, so downstream components that only declared
> `REQUIRES asio` failed to compile with `socketpair.h: No such file or directory`.
The test project defines a custom component `asio_consumer` (under
`components/asio_consumer/`) that:
- Only declares `REQUIRES asio` in its `CMakeLists.txt` (not `sock_utils`).
- Includes `asio.hpp` from a `.cpp` source.
Before the fix (where `sock_utils` was in `PRIV_REQUIRES`) the build fails at the
preprocessor stage. After the fix (`sock_utils` in `REQUIRES` and `public: true`
in `idf_component.yml`) the build succeeds.
Build with:
```bash
idf.py -DIDF_TARGET=esp32 build
```
@@ -0,0 +1,6 @@
# Regression test component: must compile with `REQUIRES asio` only.
# If asio doesn't expose sock_utils as a public dependency, including
# "asio.hpp" fails because it transitively includes "socketpair.h".
idf_component_register(SRCS "asio_consumer.cpp"
INCLUDE_DIRS "include"
REQUIRES asio)
@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "asio.hpp"
#include "asio/local/connect_pair.hpp"
#include "asio/local/stream_protocol.hpp"
#include "asio_consumer.h"
void asio_consumer_run(void)
{
asio::io_context io_context;
asio::ip::tcp::socket tcp_sock(io_context);
(void)tcp_sock;
// Exercise asio::local::connect_pair: this is the only public client-facing
// API that ultimately calls ::socketpair() (through asio's separately
// compiled TU). If sock_utils were leaking through asio's public headers,
// this would require sock_utils on the component's include path.
asio::local::stream_protocol::socket s1(io_context), s2(io_context);
asio::error_code ec;
asio::local::connect_pair(s1, s2, ec);
(void)ec;
}
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void asio_consumer_run(void);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,3 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES asio_consumer)
@@ -0,0 +1,4 @@
dependencies:
espressif/asio:
version: "^1.14.1"
override_path: "../../../"
@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "asio_consumer.h"
void app_main(void)
{
asio_consumer_run();
}
@@ -0,0 +1,3 @@
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_LWIP_IPV6=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y