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
32 changes: 6 additions & 26 deletions .github/workflows/build_binary.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,7 +17,6 @@ jobs:
runs-on: ${{ matrix.runner }}
env:
ASTREIN_VERSION: '2.0.0'
ASTREIN_SHA256: ${{ matrix.astrein-sha256 }}

strategy:
fail-fast: false
Expand All@@ -26,48 +25,29 @@ jobs:
- target: x86_64-linux-gnu
arch: x86_64
runner: ubuntu-24.04
astrein-archive: astrein-linux-x86_64.tar.gz
astrein-sha256: 'c024f62f8bb4c7b5342249de5844d19276ae92293bf8cc7edf7475280503168e'

- target: aarch64-linux-gnu
arch: aarch64
runner: ubuntu-24.04-arm
astrein-archive: astrein-linux-aarch64.tar.gz
astrein-sha256: 'bab7501555d91f355bca797a38af16d9e01067591abf20b0147a8a0985558e33'

steps:
- name: 'Checkout repository'
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: 'Validate ASTrein checksum'
run: |
if [[ ! "${ASTREIN_SHA256}" =~ ^[[:xdigit:]]{64}$ ]]; then
echo "Set the SHA-256 for ${{ matrix.astrein-archive }} after ASTrein v${ASTREIN_VERSION} is released." >&2
exit 1
fi

- name: 'Install native compiler'
run: |
sudo apt-get update
sudo apt-get install -y \
gcc-14 \
g++-14

- name: 'Download ASTrein'
run: |
curl --fail --location \
--output "${RUNNER_TEMP}/${{ matrix.astrein-archive }}" \
"https://github.com/Katze719/ASTrein/releases/download/v${ASTREIN_VERSION}/${{ matrix.astrein-archive }}"
echo "${ASTREIN_SHA256} ${RUNNER_TEMP}/${{ matrix.astrein-archive }}" \
| sha256sum --check -

mkdir -p "${RUNNER_TEMP}/astrein"
tar -xzf "${RUNNER_TEMP}/${{ matrix.astrein-archive }}" \
-C "${RUNNER_TEMP}/astrein" \
--strip-components=1
"${RUNNER_TEMP}/astrein/bin/astrein" --version
- name: 'Setup ASTrein'
id: astrein
uses: Katze719/setup-astrein@v1
with:
version: ${{ env.ASTREIN_VERSION }}

- name: 'Setup CMake'
uses: jwlawson/actions-setup-cmake@v2
Expand All@@ -81,7 +61,7 @@ jobs:
-DCMAKE_C_COMPILER=gcc-14 \
-DCMAKE_CXX_COMPILER=g++-14 \
-DCPP_BINDINGS_LINUX_ENABLE_FFI_JSON_EXPORT=ON \
-DCPP_BINDINGS_LINUX_ASTREIN_EXECUTABLE="${RUNNER_TEMP}/astrein/bin/astrein" \
-DCPP_BINDINGS_LINUX_ASTREIN_EXECUTABLE="${{ steps.astrein.outputs.path }}" \
-DCPP_BINDINGS_LINUX_FFI_JSON_OUTPUT="${GITHUB_WORKSPACE}/dist/ffi/${{ matrix.arch }}.ffi.json"

- name: 'Generate metadata'
Expand Down
42 changes: 38 additions & 4 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,9 +3,16 @@
[![Build](https://github.com/Serial-IO/cpp-bindings-linux/actions/workflows/build_binary.yml/badge.svg)](https://github.com/Serial-IO/cpp-bindings-linux/actions/workflows/build_binary.yml)
[![JSR](https://jsr.io/badges/@serial/cpp-bindings-linux)](https://jsr.io/@serial/cpp-bindings-linux)

Linux shared library for serial communication. It implements the
[`cpp-core`](https://github.com/Serial-IO/cpp-core) interface and provides functions for discovering, opening,
configuring, reading from, and writing to serial ports.
Runtime-agnostic Linux shared library for serial communication. It implements
the [`cpp-core`](https://github.com/Serial-IO/cpp-core) interface and provides
functions for discovering, opening, configuring, reading from, and writing to
serial ports.

The library exposes a C-compatible ABI and can be used from any language or
runtime that can load a GNU/Linux shared library and call C functions. Release
artifacts include machine-readable FFI metadata for generating runtime-specific
adapters, including exported symbols, types, callbacks, structs, defaults, and
API documentation.

## Requirements

Expand DownExpand Up@@ -35,6 +42,31 @@ Official release and JSR artifacts are built for these GNU/Linux targets:
| `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) |

Check the installed version with:

```sh
ldd --version
```

These versions indicate binary compatibility and do not imply that the listed
distribution releases are still supported by their vendors.

### Portable release builds

The release builds statically include the GNU C++ and compiler runtimes. They
still use the target system's glibc and therefore require glibc 2.28 or newer.
To reproduce the portable release configuration for the host architecture:
Expand DownExpand Up@@ -69,7 +101,9 @@ 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.

The optional Deno FFI smoke tests require Deno 2 and a built library:
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
library from another compatible runtime:

```sh
cd integration_tests
Expand Down
166 changes: 158 additions & 8 deletions jsr/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,35 +7,185 @@ Binaries are provided as a
[package on JSR](https://jsr.io/@serial/cpp-bindings-linux). They are serialized
as a base64 string inside the JSON file.

This package targets server-side JavaScript runtimes that can write files and
load GNU/Linux shared libraries. Deno can consume it directly from JSR; Bun and
Node.js use JSR's npm compatibility layer. Browser and edge runtimes cannot use
the native library because they do not expose native FFI access.

The package contains portable binaries for `x86_64-linux-gnu` and
`aarch64-linux-gnu`, both requiring glibc 2.28 or newer. The x86-64 artifact
uses the generic x86-64 baseline.

## 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) |

Check the installed version with:

```sh
ldd --version
```

These versions indicate binary compatibility and do not imply that the listed
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
return types, callbacks, default values, and API documentation used by
downstream FFI adapter generators.
return types, callbacks, structs, default values, and API documentation used by
runtime-specific FFI adapter generators.

This package is primarily intended as a dependency for
[`@serial/serial`](https://jsr.io/@serial/serial). However, it can also be used
independently.

## Usage

Import the JSON and write the binary data to disk. Each architecture export also
contains its matching FFI metadata:
Select the export matching the host architecture. Each export contains the
base64-encoded shared library and its matching FFI metadata. The following
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`:

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

const binary = Deno.build.arch === "aarch64" ? aarch64 : x86_64;
Deno.writeFileSync(`./${binary.filename}`, Uint8Array.fromBase64(binary.data));
const path = `./${binary.filename}`;

Deno.writeFileSync(path, Uint8Array.fromBase64(binary.data));

const library = Deno.dlopen(path, {
serialOpen: {
parameters: ["pointer", "i32", "i32", "i32", "i32", "pointer"],
result: "i64",
},
});
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:

```ts
import { dlopen } from "bun:ffi";
import { resolve } from "node:path";
import { aarch64, x86_64 } from "@serial/cpp-bindings-linux/bin";

const binary = process.arch === "arm64"
? aarch64
: process.arch === "x64"
? x86_64
: undefined;

if (!binary) {
throw new Error(`Unsupported architecture: ${process.arch}`);
}

const path = resolve(binary.filename);
await Bun.write(path, Buffer.from(binary.data, "base64"));

const library = dlopen(path, {
serialOpen: {
args: ["ptr", "i32", "i32", "i32", "i32", "ptr"],
returns: "i64",
},
});
library.close();
```

```sh
bun run example.ts
```

> [!WARNING]
> Bun currently marks its built-in
> [`bun:ffi` API](https://bun.sh/docs/runtime/ffi) as experimental.

### Node.js

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:

// The matching FFI metadata is available as `binary.ffi`.
// Now you can open the binary using for example `Deno.dlopen`...
```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";
import koffi from "koffi";
import { aarch64, x86_64 } from "@serial/cpp-bindings-linux/bin";

const binary = process.arch === "arm64"
? aarch64
: process.arch === "x64"
? x86_64
: undefined;

if (!binary) {
throw new Error(`Unsupported architecture: ${process.arch}`);
}

const path = resolve(binary.filename);
writeFileSync(path, Buffer.from(binary.data, "base64"));

const library = koffi.load(path);
library.func("serialOpen", "int64_t", [
"void *",
"int",
"int",
"int",
"int",
"void *",
]);
library.unload();
```

```sh
node example.mjs
```

These examples verify that the native library can be loaded and that its
`serialOpen` symbol can be resolved. The matching `binary.ffi` value describes
the complete set of symbols and structs for generating or configuring
runtime-specific bindings.

Non-JavaScript consumers can download the same architecture-specific `.so` and
`.ffi.json` files directly from the
[GitHub releases](https://github.com/Serial-IO/cpp-bindings-linux/releases).

> [!NOTE]
> For a more in depth guide, check out the
> [Wiki](https://github.com/Serial-IO/cpp-bindings-linux/wiki) section on how to
Expand Down
2 changes: 1 addition & 1 deletion jsr/jsr.json
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
{
"name": "@serial/cpp-bindings-linux",
"version": "",
"description": "C++ Linux Bindings for the serial library",
"description": "Runtime-agnostic GNU/Linux serial FFI binaries and API metadata",
"license": "LGPL-3.0-only",
"exports": {
"./bin": "./src/bin/index.ts"
Expand Down
Loading
Loading