Merge pull request #12 from hexedtech/fix-ci-python

New Ci for Windows and no-abi build option for python
This commit is contained in:
əlemi 2024-09-21 13:27:43 +02:00 committed by GitHub
commit d7c4ef3891
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 152 additions and 84 deletions

View file

@ -89,6 +89,11 @@ jobs:
runs-on: ${{ matrix.platform.runner }} runs-on: ${{ matrix.platform.runner }}
strategy: strategy:
matrix: matrix:
versions:
- python: 3.8
features: py-noabi
- python: 3.x
features: py
platform: platform:
- runner: windows-latest - runner: windows-latest
target: x64 target: x64
@ -101,7 +106,7 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: 3.x python-version: ${{ matrix.versions.python }}
architecture: ${{ matrix.platform.target }} architecture: ${{ matrix.platform.target }}
- name: Build wheels - name: Build wheels
uses: PyO3/maturin-action@v1 uses: PyO3/maturin-action@v1
@ -109,12 +114,12 @@ jobs:
working-directory: dist/py working-directory: dist/py
target: ${{ matrix.platform.target }} target: ${{ matrix.platform.target }}
container: 'off' container: 'off'
args: --release --out ./build args: --release --out ./build --features ${{ matrix.versions.features }}
sccache: 'true' sccache: 'true'
- name: Upload wheels - name: Upload wheels
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: codemp-py-windows-${{ matrix.platform.target }} name: codemp-${{ matrix.versions.features }}-windows-${{ matrix.platform.target }}
path: dist/py/build path: dist/py/build
macos: macos:

View file

@ -50,7 +50,7 @@ napi = { version = "2.16", features = ["full"], optional = true }
napi-derive = { version="2.16", optional = true} napi-derive = { version="2.16", optional = true}
# glue (python) # glue (python)
pyo3 = { version = "0.22", features = ["extension-module", "abi3-py38"], optional = true} pyo3 = { version = "0.22", features = ["extension-module"], optional = true}
# extra # extra
async-trait = { version = "0.1", optional = true } async-trait = { version = "0.1", optional = true }
@ -71,7 +71,8 @@ serialize = ["dep:serde", "uuid/serde"]
rust = [] # used for ci matrix rust = [] # used for ci matrix
java = ["lazy_static", "jni", "tracing-subscriber"] java = ["lazy_static", "jni", "tracing-subscriber"]
js = ["napi-build", "tracing-subscriber", "napi", "napi-derive"] js = ["napi-build", "tracing-subscriber", "napi", "napi-derive"]
py = ["pyo3", "tracing-subscriber", "pyo3-build-config"] py-noabi = ["pyo3", "tracing-subscriber", "pyo3-build-config"]
py = ["py-noabi", "pyo3/abi3-py38"]
lua = ["mlua-codemp-patch", "tracing-subscriber", "lazy_static", "serialize"] lua = ["mlua-codemp-patch", "tracing-subscriber", "lazy_static", "serialize"]
lua54 =["lua", "mlua-codemp-patch/lua54"] lua54 =["lua", "mlua-codemp-patch/lua54"]
luajit = ["lua", "mlua-codemp-patch/luajit"] luajit = ["lua", "mlua-codemp-patch/luajit"]

View file

@ -1,7 +1,7 @@
#[cfg(feature = "js")] #[cfg(feature = "js")]
extern crate napi_build; extern crate napi_build;
#[cfg(feature = "py")] #[cfg(any(feature = "py", feature = "py-noabi"))]
extern crate pyo3_build_config; extern crate pyo3_build_config;
/// The main method of the buildscript, required by some glue modules. /// The main method of the buildscript, required by some glue modules.
@ -11,7 +11,7 @@ fn main() {
napi_build::setup(); napi_build::setup();
} }
#[cfg(feature = "py")] #[cfg(any(feature = "py", feature = "py-noabi"))]
{ {
pyo3_build_config::add_extension_module_link_args(); pyo3_build_config::add_extension_module_link_args();
} }

View file

@ -1,8 +1,8 @@
[project] [project]
name = "codemp" name = "codemp"
version = "0.7.0" version = "0.7.1"
description = "code multiplexer" description = "code multiplexer"
requires-python = ">= 3.8" requires-python = ">=3.8"
license = "GPL-3.0-only" license = "GPL-3.0-only"
keywords = ["codemp", "cooperative", "rust", "python"] keywords = ["codemp", "cooperative", "rust", "python"]
authors = [ authors = [
@ -28,7 +28,7 @@ requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin" build-backend = "maturin"
[tool.maturin] [tool.maturin]
features = ["py", "pyo3/extension-module"] features = ["py"]
manifest-path = "../../Cargo.toml" manifest-path = "../../Cargo.toml"
python-source = "src" python-source = "src"

View file

@ -22,7 +22,7 @@
/// ///
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "js", napi_derive::napi(object))] #[cfg_attr(feature = "js", napi_derive::napi(object))]
#[cfg_attr(feature = "py", pyo3::pyclass(get_all))] #[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass(get_all))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct TextChange { pub struct TextChange {
/// Range start of text change, as char indexes in buffer previous state. /// Range start of text change, as char indexes in buffer previous state.
@ -43,7 +43,7 @@ impl TextChange {
} }
} }
#[cfg_attr(feature = "py", pyo3::pymethods)] #[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pymethods)]
impl TextChange { impl TextChange {
/// Returns true if this [`TextChange`] deletes existing text. /// Returns true if this [`TextChange`] deletes existing text.
/// ///

View file

@ -10,7 +10,10 @@
/// http{tls?'s':''}://{host}:{port} /// http{tls?'s':''}://{host}:{port}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[cfg_attr(feature = "js", napi_derive::napi(object))] #[cfg_attr(feature = "js", napi_derive::napi(object))]
#[cfg_attr(feature = "py", pyo3::pyclass(get_all, set_all))] #[cfg_attr(
any(feature = "py", feature = "py-noabi"),
pyo3::pyclass(get_all, set_all)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct Config { pub struct Config {
/// user identifier used to register, possibly your email /// user identifier used to register, possibly your email

View file

@ -1,12 +1,12 @@
//! ### Cursor //! ### Cursor
//! Represents the position of a remote user's cursor. //! Represents the position of a remote user's cursor.
#[cfg(feature = "py")] #[cfg(any(feature = "py", feature = "py-noabi"))]
use pyo3::prelude::*; use pyo3::prelude::*;
/// User cursor position in a buffer /// User cursor position in a buffer
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "py", pyclass)] #[cfg_attr(any(feature = "py", feature = "py-noabi"), pyclass)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
// #[cfg_attr(feature = "py", pyo3(crate = "reexported::pyo3"))] // #[cfg_attr(feature = "py", pyo3(crate = "reexported::pyo3"))]
pub struct Cursor { pub struct Cursor {

View file

@ -4,7 +4,7 @@ use codemp_proto::workspace::workspace_event::Event as WorkspaceEventInner;
/// Event in a [crate::Workspace]. /// Event in a [crate::Workspace].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "py", pyo3::pyclass)] #[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub enum Event { pub enum Event {
/// Fired when the file tree changes. /// Fired when the file tree changes.

View file

@ -18,7 +18,7 @@ use super::worker::DeltaRequest;
/// Each buffer controller internally tracks the last acknowledged state, remaining always in sync /// Each buffer controller internally tracks the last acknowledged state, remaining always in sync
/// with the server while allowing to procedurally receive changes while still sending new ones. /// with the server while allowing to procedurally receive changes while still sending new ones.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "py", pyo3::pyclass)] #[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi)] #[cfg_attr(feature = "js", napi_derive::napi)]
pub struct BufferController(pub(crate) Arc<BufferControllerInner>); pub struct BufferController(pub(crate) Arc<BufferControllerInner>);

View file

@ -4,15 +4,25 @@
use std::sync::Arc; use std::sync::Arc;
use dashmap::DashMap; use dashmap::DashMap;
use tonic::{service::interceptor::InterceptedService, transport::{Channel, Endpoint}}; use tonic::{
service::interceptor::InterceptedService,
use crate::{api::User, errors::{ConnectionResult, RemoteResult}, ext::InternallyMutable, network, workspace::Workspace}; transport::{Channel, Endpoint},
use codemp_proto::{
auth::{auth_client::AuthClient, LoginRequest},
common::{Empty, Token}, session::{session_client::SessionClient, InviteRequest, WorkspaceRequest},
}; };
#[cfg(feature = "py")] use crate::{
api::User,
errors::{ConnectionResult, RemoteResult},
ext::InternallyMutable,
network,
workspace::Workspace,
};
use codemp_proto::{
auth::{auth_client::AuthClient, LoginRequest},
common::{Empty, Token},
session::{session_client::SessionClient, InviteRequest, WorkspaceRequest},
};
#[cfg(any(feature = "py", feature = "py-noabi"))]
use pyo3::prelude::*; use pyo3::prelude::*;
/// A `codemp` client handle. /// A `codemp` client handle.
@ -22,7 +32,7 @@ use pyo3::prelude::*;
/// A new [`Client`] can be obtained with [`Client::connect`]. /// A new [`Client`] can be obtained with [`Client::connect`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "js", napi_derive::napi)] #[cfg_attr(feature = "js", napi_derive::napi)]
#[cfg_attr(feature = "py", pyclass)] #[cfg_attr(any(feature = "py", feature = "py-noabi"), pyclass)]
pub struct Client(Arc<ClientInner>); pub struct Client(Arc<ClientInner>);
#[derive(Debug)] #[derive(Debug)]
@ -42,30 +52,37 @@ impl Client {
let channel = Endpoint::from_shared(config.endpoint())?.connect().await?; let channel = Endpoint::from_shared(config.endpoint())?.connect().await?;
let mut auth = AuthClient::new(channel.clone()); let mut auth = AuthClient::new(channel.clone());
let resp = auth.login(LoginRequest { let resp = auth
username: config.username.clone(), .login(LoginRequest {
password: config.password.clone(), username: config.username.clone(),
}) password: config.password.clone(),
})
.await? .await?
.into_inner(); .into_inner();
let claims = InternallyMutable::new(resp.token); let claims = InternallyMutable::new(resp.token);
// TODO move this one into network.rs // TODO move this one into network.rs
let session = SessionClient::with_interceptor( let session =
channel, network::SessionInterceptor(claims.channel()) SessionClient::with_interceptor(channel, network::SessionInterceptor(claims.channel()));
);
Ok(Client(Arc::new(ClientInner { Ok(Client(Arc::new(ClientInner {
user: resp.user.into(), user: resp.user.into(),
workspaces: DashMap::default(), workspaces: DashMap::default(),
claims, auth, session, config claims,
auth,
session,
config,
}))) })))
} }
/// Refresh session token. /// Refresh session token.
pub async fn refresh(&self) -> RemoteResult<()> { pub async fn refresh(&self) -> RemoteResult<()> {
let new_token = self.0.auth.clone().refresh(self.0.claims.get()) let new_token = self
.0
.auth
.clone()
.refresh(self.0.claims.get())
.await? .await?
.into_inner(); .into_inner();
self.0.claims.set(new_token); self.0.claims.set(new_token);
@ -74,25 +91,36 @@ impl Client {
/// Attempt to create a new workspace with given name. /// Attempt to create a new workspace with given name.
pub async fn create_workspace(&self, name: impl AsRef<str>) -> RemoteResult<()> { pub async fn create_workspace(&self, name: impl AsRef<str>) -> RemoteResult<()> {
self.0.session self.0
.session
.clone() .clone()
.create_workspace(WorkspaceRequest { workspace: name.as_ref().to_string() }) .create_workspace(WorkspaceRequest {
workspace: name.as_ref().to_string(),
})
.await?; .await?;
Ok(()) Ok(())
} }
/// Delete an existing workspace if possible. /// Delete an existing workspace if possible.
pub async fn delete_workspace(&self, name: impl AsRef<str>) -> RemoteResult<()> { pub async fn delete_workspace(&self, name: impl AsRef<str>) -> RemoteResult<()> {
self.0.session self.0
.session
.clone() .clone()
.delete_workspace(WorkspaceRequest { workspace: name.as_ref().to_string() }) .delete_workspace(WorkspaceRequest {
workspace: name.as_ref().to_string(),
})
.await?; .await?;
Ok(()) Ok(())
} }
/// Invite user with given username to the given workspace, if possible. /// Invite user with given username to the given workspace, if possible.
pub async fn invite_to_workspace(&self, workspace_name: impl AsRef<str>, user_name: impl AsRef<str>) -> RemoteResult<()> { pub async fn invite_to_workspace(
self.0.session &self,
workspace_name: impl AsRef<str>,
user_name: impl AsRef<str>,
) -> RemoteResult<()> {
self.0
.session
.clone() .clone()
.invite_to_workspace(InviteRequest { .invite_to_workspace(InviteRequest {
workspace: workspace_name.as_ref().to_string(), workspace: workspace_name.as_ref().to_string(),
@ -104,7 +132,9 @@ impl Client {
/// List all available workspaces, also filtering between those owned and those invited to. /// List all available workspaces, also filtering between those owned and those invited to.
pub async fn list_workspaces(&self, owned: bool, invited: bool) -> RemoteResult<Vec<String>> { pub async fn list_workspaces(&self, owned: bool, invited: bool) -> RemoteResult<Vec<String>> {
let mut workspaces = self.0.session let mut workspaces = self
.0
.session
.clone() .clone()
.list_workspaces(Empty {}) .list_workspaces(Empty {})
.await? .await?
@ -112,17 +142,25 @@ impl Client {
let mut out = Vec::new(); let mut out = Vec::new();
if owned { out.append(&mut workspaces.owned) } if owned {
if invited { out.append(&mut workspaces.invited) } out.append(&mut workspaces.owned)
}
if invited {
out.append(&mut workspaces.invited)
}
Ok(out) Ok(out)
} }
/// Join and return a [`Workspace`]. /// Join and return a [`Workspace`].
pub async fn join_workspace(&self, workspace: impl AsRef<str>) -> ConnectionResult<Workspace> { pub async fn join_workspace(&self, workspace: impl AsRef<str>) -> ConnectionResult<Workspace> {
let token = self.0.session let token = self
.0
.session
.clone() .clone()
.access_workspace(WorkspaceRequest { workspace: workspace.as_ref().to_string() }) .access_workspace(WorkspaceRequest {
workspace: workspace.as_ref().to_string(),
})
.await? .await?
.into_inner(); .into_inner();

View file

@ -5,14 +5,20 @@ use std::sync::Arc;
use tokio::sync::{mpsc, oneshot, watch}; use tokio::sync::{mpsc, oneshot, watch};
use crate::{api::{controller::ControllerCallback, Controller, Cursor}, errors::ControllerResult}; use crate::{
use codemp_proto::{cursor::{CursorPosition, RowCol}, files::BufferNode}; api::{controller::ControllerCallback, Controller, Cursor},
errors::ControllerResult,
};
use codemp_proto::{
cursor::{CursorPosition, RowCol},
files::BufferNode,
};
/// A [Controller] for asynchronously sending and receiving [Cursor] event. /// A [Controller] for asynchronously sending and receiving [Cursor] event.
/// ///
/// An unique [CursorController] exists for each active [crate::Workspace]. /// An unique [CursorController] exists for each active [crate::Workspace].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "py", pyo3::pyclass)] #[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi)] #[cfg_attr(feature = "js", napi_derive::napi)]
pub struct CursorController(pub(crate) Arc<CursorControllerInner>); pub struct CursorController(pub(crate) Arc<CursorControllerInner>);
@ -31,11 +37,23 @@ impl Controller<Cursor> for CursorController {
if cursor.start > cursor.end { if cursor.start > cursor.end {
std::mem::swap(&mut cursor.start, &mut cursor.end); std::mem::swap(&mut cursor.start, &mut cursor.end);
} }
Ok(self.0.op.send(CursorPosition { Ok(self
buffer: BufferNode { path: cursor.buffer }, .0
start: RowCol { row: cursor.start.0, col: cursor.start.1 }, .op
end: RowCol { row: cursor.end.0, col: cursor.end.1 }, .send(CursorPosition {
}).await?) buffer: BufferNode {
path: cursor.buffer,
},
start: RowCol {
row: cursor.start.0,
col: cursor.start.1,
},
end: RowCol {
row: cursor.end.0,
col: cursor.end.1,
},
})
.await?)
} }
async fn try_recv(&self) -> ControllerResult<Option<Cursor>> { async fn try_recv(&self) -> ControllerResult<Option<Cursor>> {

View file

@ -56,5 +56,5 @@ pub mod lua;
pub mod js; pub mod js;
/// python bindings, built with [pyo3] /// python bindings, built with [pyo3]
#[cfg(feature = "py")] #[cfg(any(feature = "py", feature = "py-noabi"))]
pub mod python; pub mod python;

View file

@ -9,7 +9,7 @@ use crate::{
cursor::{self, worker::CursorWorker}, cursor::{self, worker::CursorWorker},
errors::{ConnectionResult, ControllerResult, RemoteResult}, errors::{ConnectionResult, ControllerResult, RemoteResult},
ext::InternallyMutable, ext::InternallyMutable,
network::Services network::Services,
}; };
use codemp_proto::{ use codemp_proto::{
@ -33,7 +33,7 @@ use uuid::Uuid;
use napi_derive::napi; use napi_derive::napi;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(feature = "py", pyo3::pyclass)] #[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)]
#[cfg_attr(feature = "js", napi)] #[cfg_attr(feature = "js", napi)]
pub struct Workspace(Arc<WorkspaceInner>); pub struct Workspace(Arc<WorkspaceInner>);
@ -59,7 +59,8 @@ impl Workspace {
claims: tokio::sync::watch::Receiver<codemp_proto::common::Token>, // TODO ughh receiving this claims: tokio::sync::watch::Receiver<codemp_proto::common::Token>, // TODO ughh receiving this
) -> ConnectionResult<Self> { ) -> ConnectionResult<Self> {
let workspace_claim = InternallyMutable::new(token); let workspace_claim = InternallyMutable::new(token);
let services = Services::try_new(&config.endpoint(), claims, workspace_claim.channel()).await?; let services =
Services::try_new(&config.endpoint(), claims, workspace_claim.channel()).await?;
let ws_stream = services.ws().attach(Empty {}).await?.into_inner(); let ws_stream = services.ws().attach(Empty {}).await?.into_inner();
let (tx, rx) = mpsc::channel(128); let (tx, rx) = mpsc::channel(128);
@ -126,12 +127,12 @@ impl Workspace {
let (tx, rx) = mpsc::channel(256); let (tx, rx) = mpsc::channel(256);
let mut req = tonic::Request::new(tokio_stream::wrappers::ReceiverStream::new(rx)); let mut req = tonic::Request::new(tokio_stream::wrappers::ReceiverStream::new(rx));
req.metadata_mut() req.metadata_mut().insert(
.insert( "buffer",
"buffer", tonic::metadata::MetadataValue::try_from(credentials.token).map_err(|e| {
tonic::metadata::MetadataValue::try_from(credentials.token) tonic::Status::internal(format!("failed representing token to string: {e}"))
.map_err(|e| tonic::Status::internal(format!("failed representing token to string: {e}")))?, })?,
); );
let stream = self.0.services.buf().attach(req).await?.into_inner(); let stream = self.0.services.buf().attach(req).await?.into_inner();
let worker = BufferWorker::new(self.0.user.id, path); let worker = BufferWorker::new(self.0.user.id, path);
@ -282,14 +283,18 @@ impl Workspace {
/// A filter may be applied, and it may be strict (equality check) or not (starts_with check). /// A filter may be applied, and it may be strict (equality check) or not (starts_with check).
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120 // #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
pub fn filetree(&self, filter: Option<&str>, strict: bool) -> Vec<String> { pub fn filetree(&self, filter: Option<&str>, strict: bool) -> Vec<String> {
self.0.filetree.iter() self.0
.filter(|f| filter.map_or(true, |flt| { .filetree
if strict { .iter()
f.as_str() == flt .filter(|f| {
} else { filter.map_or(true, |flt| {
f.starts_with(flt) if strict {
} f.as_str() == flt
})) } else {
f.starts_with(flt)
}
})
})
.map(|f| f.clone()) .map(|f| f.clone())
.collect() .collect()
} }
@ -315,9 +320,7 @@ impl Workspace {
match ev { match ev {
// user // user
WorkspaceEventInner::Join(UserJoin { user }) => { WorkspaceEventInner::Join(UserJoin { user }) => {
inner inner.users.insert(user.id.uuid(), user.into());
.users
.insert(user.id.uuid(), user.into());
} }
WorkspaceEventInner::Leave(UserLeave { user }) => { WorkspaceEventInner::Leave(UserLeave { user }) => {
inner.users.remove(&user.id.uuid()); inner.users.remove(&user.id.uuid());
@ -364,8 +367,8 @@ impl Drop for WorkspaceInner {
} }
} }
#[cfg_attr(feature = "py", pyo3::pyclass(eq, eq_int))] #[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass(eq, eq_int))]
#[cfg_attr(feature = "py", derive(PartialEq))] #[cfg_attr(any(feature = "py", feature = "py-noabi"), derive(PartialEq))]
pub enum DetachResult { pub enum DetachResult {
NotAttached, NotAttached,
Detaching, Detaching,