Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion .github/workflows/build_binary.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,7 +39,7 @@ jobs:
needs: ['setup-matrix']
runs-on: ${{ matrix.runner }}
env:
ASTREIN_VERSION: '2.0.0'
ASTREIN_VERSION: '3.0.0'

strategy:
fail-fast: false
Expand DownExpand Up@@ -178,6 +178,11 @@ jobs:
with:
fetch-depth: 0

- name: 'Enable Git metadata in the build container'
run: |
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
git rev-parse --verify HEAD

- name: 'Configure portable release'
run: |
cmake -S . \
Expand Down
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,7 +65,7 @@ set(
CPMAddPackage(
NAME cpp_core
GITHUB_REPOSITORY Serial-IO/cpp-core
GIT_TAG v2.0.1
GIT_TAG v3.0.0
OPTIONS
"CMAKE_EXPORT_COMPILE_COMMANDS OFF"
)
Expand DownExpand Up@@ -257,6 +257,9 @@ if(TEST_SOURCES)
)

target_compile_features(cpp_bindings_linux_tests PRIVATE cxx_std_26)
target_compile_definitions(cpp_bindings_linux_tests PRIVATE
CPP_BINDINGS_LINUX_TEST_VERSION="${GIT_DESCRIBE}"
)

include(GoogleTest)
gtest_discover_tests(cpp_bindings_linux_tests)
Expand Down
36 changes: 20 additions & 16 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,8 @@ API documentation.
- Ninja
- A compiler with sufficient C++26 support

CMake downloads `cpp-core` and GoogleTest automatically during configuration.
CMake downloads `cpp-core` **v3.0.0** and GoogleTest automatically during
configuration.

## Build

Expand All@@ -37,24 +38,24 @@ The shared library is written to `build/libcpp_bindings_linux.so`.

Official release and JSR artifacts are built for these GNU/Linux targets:

| Target | CPU baseline | Minimum glibc |
| --- | --- | --- |
| `x86_64-linux-gnu` | generic x86-64 | 2.28 |
| `aarch64-linux-gnu` | ARMv8-A | 2.28 |
| Target | CPU baseline | Minimum glibc |
| ------------------- | -------------- | ------------- |
| `x86_64-linux-gnu` | generic x86-64 | 2.28 |
| `aarch64-linux-gnu` | ARMv8-A | 2.28 |

### Binary compatibility

The prebuilt binaries require **glibc 2.28 or newer**. Compatibility depends on
the installed glibc version rather than the distribution name. Common release
baselines are shown below for orientation:

| Distribution | Release baseline |
| --- | --- |
| Debian | 10+ |
| Ubuntu | 20.04 LTS+ |
| RHEL / Rocky Linux / AlmaLinux | 8+ |
| Fedora | 29+ |
| openSUSE Leap | 15.x (not compatible by default) |
| Distribution | Release baseline |
| ------------------------------ | -------------------------------- |
| Debian | 10+ |
| Ubuntu | 20.04 LTS+ |
| RHEL / Rocky Linux / AlmaLinux | 8+ |
| Fedora | 29+ |
| openSUSE Leap | 15.x (not compatible by default) |

Check the installed version with:

Expand All@@ -79,8 +80,9 @@ cmake -S . -B build -G Ninja \
cmake --build build --target cpp_bindings_linux
```

The CI release builds use pinned `manylinux_2_28` images with GCC 14. GCC 16
is used separately to generate the ASTrein FFI metadata.
The CI release builds use pinned `manylinux_2_28` images with GCC 14. The FFI
metadata is generated separately with ASTrein 3.0.0 and a GCC 14 compile
context.

To select a specific compiler, add it while configuring, for example:

Expand All@@ -99,7 +101,8 @@ cmake --build --preset linux-gcc-release --target cpp_bindings_linux_tests
ctest --test-dir build --output-on-failure
```

Tests that require a serial device use `SERIAL_TEST_PORT`. They are skipped when no suitable device is available.
Tests that require a serial device use `SERIAL_TEST_PORT`. They are skipped when
no suitable device is available.

The optional runtime integration smoke tests currently use Deno 2 as their FFI
test harness and require a built library. Deno is not required to consume the
Expand All@@ -112,4 +115,5 @@ deno task test

## License

This project is licensed under the [GNU Lesser General Public License v3.0](LICENSE).
This project is licensed under the
[GNU Lesser General Public License v3.0](LICENSE).
24 changes: 20 additions & 4 deletions integration_tests/ffi_bindings.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,20 +7,36 @@ export type LoadedLibrary = Deno.DynamicLibrary<typeof symbols>;
export type SerialLib = LoadedLibrary["symbols"];

const symbols = {
meta: {
parameters: ["pointer"] as const,
result: "void" as const,
},
serialReadUntilSequence: {
parameters: ["i64", "pointer", "i32", "pointer", "pointer", "i32", "pointer"] as const,
result: "i32" as const,
},
serialWaitForDrain: {
parameters: ["i64", "pointer"] as const,
result: "i32" as const,
},
serialSetEventCallback: {
parameters: ["pointer", "pointer"] as const,
result: "i32" as const,
},
serialOpen: {
parameters: ["pointer", "i32", "i32", "i32", "i32", "pointer"] as const,
parameters: ["pointer", "pointer", "pointer"] as const,
result: "i64" as const,
},
serialClose: {
parameters: ["i64", "pointer"] as const,
result: "i32" as const,
},
serialRead: {
parameters: ["i64", "pointer", "i32", "i32", "i32", "pointer"] as const,
parameters: ["i64", "pointer", "i32", "pointer", "pointer"] as const,
result: "i32" as const,
},
serialWrite: {
parameters: ["i64", "pointer", "i32", "i32", "i32", "pointer"] as const,
parameters: ["i64", "pointer", "i32", "pointer", "pointer"] as const,
result: "i32" as const,
},
};
Expand All@@ -38,7 +54,7 @@ export async function loadSerialLib(

// Try to find the library in common build locations
const possiblePaths = [
libraryPath,
libraryPath ?? Deno.env.get("SERIAL_LIBRARY_PATH"),
"../build/libcpp_bindings_linux.so",
"../build/libcpp_bindings_linux.so.0",
"../build/libcpp_bindings_linux.so.0.0.0",
Expand Down
29 changes: 28 additions & 1 deletion integration_tests/integration_test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@
* - verify that it can be cleanly unloaded again
*/

import { assertExists } from "@std/assert";
import { assertEquals, assertExists } from "@std/assert";
import { type LoadedLibrary, loadSerialLib, type SerialLib } from "./ffi_bindings.ts";

let lib: SerialLib | null = null;
Expand All@@ -30,6 +30,33 @@ Deno.test({
sanitizeOps: false,
});

Deno.test({
name: "cpp-core v3 configuration ABI",
fn() {
assertExists(lib);
const config = new Int32Array([9600, 8, 0, 0, 0]);
const timeout = new Int32Array([10, 1]);
const invalidTimeout = new Int32Array([-1, 1]);
const port = new TextEncoder().encode("/dev/ttyNONEXISTENT99999\0");
const buffer = new Uint8Array(4);
const pointer = Deno.UnsafePointer.of;

assertEquals(Number(lib.serialOpen(pointer(port), pointer(config), null)), -200);
assertEquals(Number(lib.serialOpen(pointer(port), null, null)), -405);
assertEquals(lib.serialRead(-1n, pointer(buffer), 4, pointer(timeout), null), -201);
assertEquals(lib.serialWrite(-1n, pointer(buffer), 4, pointer(timeout), null), -201);
assertEquals(lib.serialRead(-1n, pointer(buffer), 4, null, null), -105);
assertEquals(lib.serialWrite(-1n, pointer(buffer), 4, pointer(invalidTimeout), null), -105);
assertEquals(
lib.serialReadUntilSequence(-1n, pointer(buffer), 4, pointer(timeout), pointer(buffer), 0, null),
-304,
);
assertEquals(lib.serialWaitForDrain(-1n, null), -201);
assertEquals(lib.serialSetEventCallback(null, null), 0);
lib.meta(null);
},
});

Deno.test({
name: "Unload cpp-bindings-linux library",
async fn() {
Expand Down
5 changes: 3 additions & 2 deletions jsr/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,8 +42,9 @@ distribution releases are still supported by their vendors.
## FFI metadata

It also includes cpp-core FFI API metadata generated with
[ASTrein](https://github.com/Katze719/ASTrein) at `bin/x86_64/ffi.json` and
`bin/aarch64/ffi.json`. It describes the exported C symbols, parameter and
[ASTrein 3.0.0](https://github.com/Katze719/ASTrein/releases/tag/v3.0.0) at `bin/x86_64/ffi.json` and
`bin/aarch64/ffi.json`, using the `astrein_ffi_api` schema version 3.
It describes the exported C symbols, parameter and
return types, callbacks, structs, default values, and API documentation used by
runtime-specific FFI adapter generators.

Expand Down
1 change: 0 additions & 1 deletion src/detail/parse_stop_bits.hpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,6 @@ inline auto parseStopBits(int stop_bits, ErrorCallbackT error_callback, StatusCo
switch (stop_bits)
{
case 0:
case 1:
return StopBits::kOne;
case 2:
return StopBits::kTwo;
Expand Down
13 changes: 11 additions & 2 deletions src/detail/read_impl.hpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,10 +17,19 @@

namespace cpp_bindings_linux::detail
{
inline auto readImpl(int64_t handle, void *buffer, int buffer_size, int timeout_ms, int multiplier,
const unsigned char *terminator, int terminator_size, ErrorCallbackT error_callback) -> int
inline auto readImpl(int64_t handle, std::uint8_t *buffer, int buffer_size,
const cpp_core::SerialTimeoutConfig *timeout_config, const unsigned char *terminator,
int terminator_size, ErrorCallbackT error_callback) -> int
{
const auto callback = effectiveErrorCallback(error_callback);
const auto timeout_status = cpp_core::validateTimeoutConfig<int>(timeout_config, callback);
if (timeout_status < 0)
{
return timeout_status;
}
const int timeout_ms = timeout_config->timeout_ms;
const int multiplier = timeout_config->multiplier;

const auto buffer_status = cpp_core::validateBuffer<int>(buffer, buffer_size, callback);
if (buffer_status < 0)
{
Expand Down
3 changes: 2 additions & 1 deletion src/detail/termios2.hpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,8 @@
#endif

#ifndef BOTHER
#define BOTHER 0x010000
// Linux asm-generic/termbits.h: select the explicit c_ispeed/c_ospeed values.
#define BOTHER 0x00001000
#endif

#ifndef CRTSCTS
Expand Down
2 changes: 1 addition & 1 deletion src/detail/validate_baudrate_value.hpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ namespace cpp_bindings_linux::detail
{
inline auto validateBaudrateValue(int baudrate) -> cpp_core::Status
{
if (cpp_core::SerialConfig::tryMake(baudrate, 8))
if (cpp_core::SerialConfig::tryMake(baudrate, cpp_core::DataBits::kEight))
{
return cpp_core::ok();
}
Expand Down
2 changes: 1 addition & 1 deletion src/detail/validate_data_bits_value.hpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,7 +11,7 @@ namespace cpp_bindings_linux::detail
{
inline auto validateDataBitsValue(int data_bits) -> cpp_core::Status
{
if (cpp_core::SerialConfig::tryMake(300, data_bits))
if (cpp_core::SerialConfig::tryMake(300, static_cast<cpp_core::DataBits>(data_bits)))
{
return cpp_core::ok();
}
Expand Down
12 changes: 10 additions & 2 deletions src/detail/write_impl.hpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,10 +16,18 @@

namespace cpp_bindings_linux::detail
{
inline auto writeImpl(int64_t handle, const void *buffer, int buffer_size, int timeout_ms, int multiplier,
ErrorCallbackT error_callback) -> int
inline auto writeImpl(int64_t handle, const std::uint8_t *buffer, int buffer_size,
const cpp_core::SerialTimeoutConfig *timeout_config, ErrorCallbackT error_callback) -> int
{
const auto callback = effectiveErrorCallback(error_callback);
const auto timeout_status = cpp_core::validateTimeoutConfig<int>(timeout_config, callback);
if (timeout_status < 0)
{
return timeout_status;
}
const int timeout_ms = timeout_config->timeout_ms;
const int multiplier = timeout_config->multiplier;

const auto buffer_status = cpp_core::validateBuffer<int>(buffer, buffer_size, callback);
if (buffer_status < 0)
{
Expand Down
29 changes: 29 additions & 0 deletions src/meta.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
// Load the binding's generated version before cpp-core's identically guarded header.
#include "version.hpp"

#include <cpp_core/interface/meta.h>

MODULE_API void meta(cpp_core::Meta *out)
{
if (out != nullptr)
{
*out = cpp_core::Meta{
.major = version::MAJOR,
.minor = version::MINOR,
.patch = version::PATCH,
.commits_since_tag = version::GIT_COMMIT_COUNT,
.is_dirty = version::GIT_IS_DIRTY ? 1 : 0,
.version_string = version::VERSION,
.prerelease = version::PRERELEASE,
.prerelease_type = version::PRERELEASE_TYPE,
.prerelease_number = version::PRERELEASE_NUMBER,
.git_tag = version::GIT_TAG,
.git_describe_hash = version::GIT_DESCRIBE_HASH,
.git_commit_hash_short = version::GIT_COMMIT_HASH_SHORT,
.git_commit_hash_full = version::GIT_COMMIT_HASH_FULL,
.git_commit_date = version::GIT_COMMIT_DATE,
.git_branch = version::GIT_BRANCH,
.git_dirty_suffix = version::GIT_DIRTY_SUFFIX,
};
}
}
Loading
Loading