Compare commits

...

6 Commits

Author SHA1 Message Date
d2weber
f6ecc575e5 Add generated folder 2026-07-16 09:29:17 +02:00
d2weber
b84b669ba4 use newer yerpc 2026-07-16 09:21:26 +02:00
d2weber
43dae389d7 feat: Generate qt jsonrpc bindings 2026-07-16 09:20:56 +02:00
d2weber
1d69a547df fmt 2026-06-19 19:12:40 +02:00
d2weber
65d72f052a fixup: renames 2026-06-19 18:49:36 +02:00
d2weber
0c064a52fc feat: generate jsonrpc headers at build time 2026-06-19 18:39:33 +02:00
32 changed files with 190 additions and 44 deletions

View File

@@ -33,7 +33,7 @@ jobs:
run: npm install -g npm@latest
- name: Install dependencies without running scripts
working-directory: deltachat-jsonrpc/typescript
working-directory: deltachat-jsonrpc-bindings/typescript
run: npm install --ignore-scripts
- name: Package
@@ -43,5 +43,5 @@ jobs:
npm pack .
- name: Publish
working-directory: deltachat-jsonrpc/typescript
working-directory: deltachat-jsonrpc-bindings/typescript
run: npm publish --provenance deltachat-jsonrpc-client-* --access public

View File

@@ -30,16 +30,16 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-bin: false
- name: npm install
working-directory: deltachat-jsonrpc/typescript
working-directory: deltachat-jsonrpc-bindings/typescript
run: npm install
- name: Build TypeScript, run Rust tests, generate bindings
working-directory: deltachat-jsonrpc/typescript
working-directory: deltachat-jsonrpc-bindings/typescript
run: npm run build
- name: Run integration tests
working-directory: deltachat-jsonrpc/typescript
working-directory: deltachat-jsonrpc-bindings/typescript
run: npm run test
env:
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
- name: Run linter
working-directory: deltachat-jsonrpc/typescript
working-directory: deltachat-jsonrpc-bindings/typescript
run: npm run prettier:check

View File

@@ -81,7 +81,7 @@ jobs:
defaults:
run:
working-directory: ./deltachat-jsonrpc/typescript
working-directory: ./deltachat-jsonrpc-bindings/typescript
steps:
- uses: actions/checkout@v6
@@ -104,7 +104,7 @@ jobs:
mkdir -p "$HOME/.ssh"
echo "${{ secrets.JS_JSONRPC_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
chmod 600 "$HOME/.ssh/key"
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:/var/www/html/js.jsonrpc.delta.chat/"
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc-bindings/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:/var/www/html/js.jsonrpc.delta.chat/"
build-cffi:
runs-on: ubuntu-latest

View File

@@ -2,45 +2,51 @@ cmake_minimum_required(VERSION 3.16)
project(deltachat LANGUAGES C)
include(GNUInstallDirs)
option(WITH_JSONRPC_BINDINGS "Generate jsonrpc bindings" OFF)
find_program(CARGO cargo)
if(APPLE)
set(DYNAMIC_EXT "dylib")
set(DYNAMIC_EXT "dylib")
elseif(UNIX)
set(DYNAMIC_EXT "so")
set(DYNAMIC_EXT "so")
else()
set(DYNAMIC_EXT "dll")
set(DYNAMIC_EXT "dll")
endif()
if(DEFINED ENV{CARGO_BUILD_TARGET})
set(ARCH_DIR "$ENV{CARGO_BUILD_TARGET}")
set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/$ENV{CARGO_BUILD_TARGET}/release")
else()
set(ARCH_DIR "./")
set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/release")
endif()
add_custom_command(
OUTPUT
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
COMMAND
PREFIX=${CMAKE_INSTALL_PREFIX}
LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR}
INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR}
${CARGO} build --target-dir=${CMAKE_BINARY_DIR}/target --release
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi
)
if(WITH_JSONRPC_BINDINGS)
set(JSONRPC_ARGS --package deltachat-jsonrpc-bindings)
endif()
add_custom_target(
lib_deltachat
ALL
DEPENDS
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
lib_deltachat
ALL
COMMAND
${CMAKE_COMMAND} -E env
CARGO_TARGET_DIR="${CMAKE_BINARY_DIR}/target"
PREFIX="${CMAKE_INSTALL_PREFIX}"
LIBDIR="${CMAKE_INSTALL_FULL_LIBDIR}"
INCLUDEDIR="${CMAKE_INSTALL_FULL_INCLUDEDIR}"
${CARGO} build --release --package deltachat_ffi ${JSONRPC_ARGS}
WORKING_DIRECTORY
"${CMAKE_CURRENT_SOURCE_DIR}"
)
install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.${DYNAMIC_EXT}" DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
install(FILES "deltachat-ffi/deltachat.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
install(FILES "${CARGO_OUT_DIR}/libdeltachat.a" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
install(FILES "${CARGO_OUT_DIR}/libdeltachat.${DYNAMIC_EXT}" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
install(FILES "${CARGO_OUT_DIR}/pkgconfig/deltachat.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
if(WITH_JSONRPC_BINDINGS)
set(JSONRPC_HPP "${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc")
install(FILES "${JSONRPC_HPP}/generated/types.hpp" "${JSONRPC_HPP}/generated/client.hpp"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc/generated")
install(FILES "${JSONRPC_HPP}/cffi_client.hpp"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc")
endif()

15
Cargo.lock generated
View File

@@ -1478,6 +1478,13 @@ dependencies = [
"yerpc",
]
[[package]]
name = "deltachat-jsonrpc-bindings"
version = "2.54.0-dev"
dependencies = [
"deltachat-jsonrpc",
]
[[package]]
name = "deltachat-repl"
version = "2.54.0-dev"
@@ -7507,8 +7514,7 @@ dependencies = [
[[package]]
name = "yerpc"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dc24983fbe850227bfc1de89bf8cbfb3e2463afc322e0de2f155c4c23d06445"
source = "git+https://github.com/d2weber/yerpc.git?branch=qt_bindings#ba7aead1ffa49dc89bb85322aba38a65a0694e7b"
dependencies = [
"anyhow",
"async-channel 1.9.0",
@@ -7526,9 +7532,8 @@ dependencies = [
[[package]]
name = "yerpc_derive"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d8560d021437420316370db865e44c000bf86380b47cf05e49be9d652042bf5"
version = "0.6.4"
source = "git+https://github.com/d2weber/yerpc.git?branch=qt_bindings#ba7aead1ffa49dc89bb85322aba38a65a0694e7b"
dependencies = [
"convert_case",
"darling",

View File

@@ -130,6 +130,7 @@ members = [
"deltachat-ffi",
"deltachat_derive",
"deltachat-jsonrpc",
"deltachat-jsonrpc-bindings",
"deltachat-rpc-server",
"deltachat-ratelimit",
"deltachat-repl",
@@ -184,6 +185,7 @@ base64 = "0.22"
chrono = { version = "0.4.44", default-features = false }
deltachat-contact-tools = { path = "deltachat-contact-tools" }
deltachat-jsonrpc = { path = "deltachat-jsonrpc", default-features = false }
deltachat-jsonrpc-bindings = { path = "deltachat-jsonrpc-bindings", default-features = false }
deltachat = { path = ".", default-features = false }
futures = "0.3.32"
futures-lite = "2.6.1"
@@ -203,7 +205,7 @@ thiserror = "2"
tokio = "1"
tokio-util = "0.7.18"
tracing-subscriber = "0.3"
yerpc = "0.6.4"
yerpc = { git="https://github.com/d2weber/yerpc.git", branch="qt_bindings" }
[features]
default = ["vendored"]

1
deltachat-jsonrpc-bindings/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
generated

View File

@@ -0,0 +1,14 @@
[package]
name = "deltachat-jsonrpc-bindings"
version = "2.54.0-dev"
description = "Autogenerate DeltaChat JSON-RPC API bindings at build time"
edition = "2024"
license = "MPL-2.0"
repository = "https://github.com/chatmail/core"
[build-dependencies]
deltachat-jsonrpc = { workspace = true }
[features]
default = ["vendored"]
vendored = ["deltachat-jsonrpc/vendored"]

View File

@@ -0,0 +1,7 @@
use deltachat_jsonrpc::api::{write_qt_bindings, write_ts_bindings};
use std::path::Path;
fn main() {
write_ts_bindings(Path::new("typescript/generated"));
write_qt_bindings(Path::new("qt/generated"), "deltachat");
}

View File

@@ -0,0 +1 @@
generated

View File

@@ -0,0 +1,104 @@
#pragma once
#include "deltachat-jsonrpc/generated/types.hpp"
#include "deltachat-jsonrpc/generated/client.hpp"
#include "deltachat.h"
#include <cstdint>
#include <mutex>
#include <thread>
namespace deltachat {
class CffiTransport : public Transport {
public:
explicit CffiTransport(dc_accounts_t* accounts)
: jsonrpc_(dc_jsonrpc_init(accounts))
{
if (!jsonrpc_) std::abort();
thread_ = std::thread([this] { run(); });
}
virtual ~CffiTransport() {
done_ = true;
// Unblock dc_jsonrpc_next_response by sending a dummy request
if (jsonrpc_) dc_jsonrpc_request(jsonrpc_, "{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"get_system_info\"}");
if (thread_.joinable()) thread_.join();
std::lock_guard lk(mu_);
for (auto& [id, prom] : pending_) {
prom.set_value({{}, "Transport destructed", -32060});
}
pending_.clear();
if (jsonrpc_) dc_jsonrpc_unref(jsonrpc_);
}
virtual std::future<Result<QJsonValue>> send(const QString method, const QJsonValue params) override {
uint32_t id = next_id_++;
QJsonObject envelope{
{"jsonrpc", "2.0"},
{"id", static_cast<qint64>(id)},
{"method", method},
{"params", params},
};
std::promise<Result<QJsonValue>> prom;
std::future<Result<QJsonValue>> fut = prom.get_future();
{
std::lock_guard lk(mu_);
pending_[id] = std::move(prom);
}
QByteArray json = QJsonDocument(envelope).toJson(QJsonDocument::Compact);
dc_jsonrpc_request(jsonrpc_, json.constData());
return fut;
}
private:
void run() {
while (!done_) {
char* raw_json = dc_jsonrpc_next_response(jsonrpc_);
if (!raw_json) {
break;
}
QByteArray json{raw_json};
dc_str_unref(raw_json);
if (done_) break;
QJsonObject obj = QJsonDocument::fromJson(json).object();
if (!obj["id"].isDouble()) {
qCritical() << "No valid rpc id in" << QString{json};
continue;
}
uint32_t id = static_cast<uint32_t>(obj["id"].toInt());
std::promise<Result<QJsonValue>> prom;
{
std::lock_guard<std::mutex> lk(mu_);
if (auto nh = pending_.extract(id)) {
prom = std::move(nh.mapped());
} else {
qCritical() << "Could not map response" << QString{json};
continue;
}
}
prom.set_value(parseResult(obj));
}
}
private:
dc_jsonrpc_instance_t* jsonrpc_;
std::thread thread_;
std::mutex mu_;
std::atomic<uint32_t> next_id_{1};
std::atomic<bool> done_{false};
std::unordered_map<uint32_t, std::promise<Result<QJsonValue>>> pending_;
};
class CffiDeltaChat : public RawClient {
public:
explicit CffiDeltaChat(dc_accounts_t* accounts)
: RawClient(std::make_unique<CffiTransport>(accounts)) {}
};
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View File

@@ -157,7 +157,7 @@ impl CommandApi {
}
}
#[rpc(all_positional, ts_outdir = "typescript/generated")]
#[rpc(all_positional)]
impl CommandApi {
/// Test function.
async fn sleep(&self, delay: f64) {

View File

@@ -56,7 +56,7 @@ for (const { folder_name, package_name } of platform_package_names) {
if (is_local) {
package_json.peerDependencies["@deltachat/jsonrpc-client"] =
`file:${join(expected_cwd, "/../../deltachat-jsonrpc/typescript")}`;
`file:${join(expected_cwd, "/../../deltachat-jsonrpc-bindings/typescript")}`;
} else {
package_json.peerDependencies["@deltachat/jsonrpc-client"] = "*";
}

View File

@@ -51,6 +51,7 @@
./deltachat-contact-tools
./deltachat-ffi
./deltachat-jsonrpc
./deltachat-jsonrpc-bindings
./deltachat-ratelimit
./deltachat-repl
./deltachat-rpc-client

View File

@@ -67,13 +67,14 @@ def main():
parser.add_argument("newversion")
json_list = [
"deltachat-jsonrpc/typescript/package.json",
"deltachat-jsonrpc-bindings/typescript/package.json",
"deltachat-rpc-server/npm-package/package.json",
]
toml_list = [
"Cargo.toml",
"deltachat-ffi/Cargo.toml",
"deltachat-jsonrpc/Cargo.toml",
"deltachat-jsonrpc-bindings/Cargo.toml",
"deltachat-rpc-server/Cargo.toml",
"deltachat-repl/Cargo.toml",
"python/pyproject.toml",