Skip to content
Merged
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 number Diff line number Diff 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 Down Expand 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
7 changes: 5 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff 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 Down Expand Up @@ -230,7 +230,7 @@ if(CPP_BINDINGS_LINUX_STATIC_CXX_RUNTIME)
)
endif()

file(GLOB SRC_UNIT_TESTS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.test.cpp")
file(GLOB SRC_UNIT_TESTS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.test.cpp")
file(GLOB TESTS_INTEGRATION "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.test.cpp")
file(GLOB TEST_HELPER_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/test_helpers/*.cpp")
set(TEST_SOURCES ${SRC_UNIT_TESTS} ${TESTS_INTEGRATION} ${TEST_HELPER_SOURCES})
Expand All @@ -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 number Diff line number Diff 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).
36 changes: 26 additions & 10 deletions integration_tests/ffi_bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,39 @@ export type LoadedLibrary = Deno.DynamicLibrary<typeof symbols>;
export type SerialLib = LoadedLibrary["symbols"];

const symbols = {
meta: {
parameters: ["pointer"],
result: "void",
},
serialReadUntilSequence: {
parameters: ["i64", "pointer", "i32", "pointer", "pointer", "i32", "pointer"],
result: "i32",
},
serialWaitForDrain: {
parameters: ["i64", "pointer"],
result: "i32",
},
serialSetEventCallback: {
parameters: ["pointer", "pointer"],
result: "i32",
},
serialOpen: {
parameters: ["pointer", "i32", "i32", "i32", "i32", "pointer"] as const,
result: "i64" as const,
parameters: ["pointer", "pointer", "pointer"],
result: "i64",
},
Comment thread
Mqxx marked this conversation as resolved.
serialClose: {
parameters: ["i64", "pointer"] as const,
result: "i32" as const,
parameters: ["i64", "pointer"],
result: "i32",
},
serialRead: {
parameters: ["i64", "pointer", "i32", "i32", "i32", "pointer"] as const,
result: "i32" as const,
parameters: ["i64", "pointer", "i32", "pointer", "pointer"],
result: "i32",
},
serialWrite: {
parameters: ["i64", "pointer", "i32", "i32", "i32", "pointer"] as const,
result: "i32" as const,
parameters: ["i64", "pointer", "i32", "pointer", "pointer"],
result: "i32",
},
};
} as const;

/**
* Load the cpp-bindings-linux shared library
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 number Diff line number Diff 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 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
41 changes: 13 additions & 28 deletions jsr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ 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 @@ -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 All @@ -59,11 +60,10 @@ examples write the library to disk, load it, and release it again.

### Deno

Deno provides native JSR imports and the built-in `Deno.dlopen` FFI API. Save
this as `example.ts`:
Deno provides native JSR imports and the built-in `Deno.dlopen` FFI API:

```ts
import { aarch64, x86_64 } from "jsr:@serial/cpp-bindings-linux/bin";
import { aarch64, x86_64 } from "@serial/cpp-bindings-linux/bin";

const binary = Deno.build.arch === "aarch64" ? aarch64 : x86_64;
const path = `./${binary.filename}`;
Expand All @@ -79,21 +79,13 @@ const library = Deno.dlopen(path, {
library.close();
```

Run it with write and FFI permissions:

```sh
deno run --allow-write --allow-ffi example.ts
```

### Bun

Add the package through JSR's npm compatibility layer:

```sh
bunx jsr add @serial/cpp-bindings-linux
```

Then use Bun's built-in `bun:ffi` and `Bun.write` APIs:
Use Bun's built-in `bun:ffi` and `Bun.write` APIs:

```ts
import { dlopen } from "bun:ffi";
Expand Down Expand Up @@ -135,13 +127,6 @@ bun run example.ts
Node.js does not provide a general-purpose C FFI API. This example uses
[Koffi](https://koffi.dev/), together with JSR's npm compatibility layer:

```sh
npx jsr add @serial/cpp-bindings-linux
npm install koffi
```

Save this as `example.mjs`:

```js
import { writeFileSync } from "node:fs";
import { resolve } from "node:path";
Expand Down
Loading
Loading