From f6c0b878cb49023e87ce104b787d38cf7a88688d Mon Sep 17 00:00:00 2001 From: cschen Date: Thu, 19 Sep 2024 21:32:46 +0200 Subject: [PATCH 1/5] feat: add specific feature flag to specify building python without abi. This is needed because if we want to ship to sublime, having the `pyo3/abi-*` feature enabled will result in a wheel that needs the `python3.dll` (f****g windows) which sublime does not ship. This works fine for Linux and Mac Os, but for windows we will need to build without abi and for Python3.8 directly for sublime specifically and also with the abi for the generic python wheel to be used everywhere else. --- Cargo.toml | 5 ++- build.rs | 4 +- dist/py/pyproject.toml | 4 +- src/api/change.rs | 6 +-- src/api/config.rs | 5 ++- src/api/cursor.rs | 4 +- src/api/event.rs | 2 +- src/buffer/controller.rs | 2 +- src/client.rs | 94 ++++++++++++++++++++++++++++------------ src/cursor/controller.rs | 34 +++++++++++---- src/ffi/mod.rs | 16 +++---- src/workspace.rs | 47 ++++++++++---------- 12 files changed, 143 insertions(+), 80 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2698072..11d9853 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ napi = { version = "2.16", features = ["full"], optional = true } napi-derive = { version="2.16", optional = true} # glue (python) -pyo3 = { version = "0.22", features = ["extension-module", "abi3-py38"], optional = true} +pyo3 = { version = "0.22", features = ["extension-module"], optional = true} # extra async-trait = { version = "0.1", optional = true } @@ -71,7 +71,8 @@ serialize = ["dep:serde", "uuid/serde"] rust = [] # used for ci matrix java = ["lazy_static", "jni", "tracing-subscriber"] 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"] lua54 =["lua", "mlua-codemp-patch/lua54"] luajit = ["lua", "mlua-codemp-patch/luajit"] diff --git a/build.rs b/build.rs index fa74299..3dee00e 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,7 @@ #[cfg(feature = "js")] extern crate napi_build; -#[cfg(feature = "py")] +#[cfg(any(feature = "py", feature = "py-noabi"))] extern crate pyo3_build_config; /// The main method of the buildscript, required by some glue modules. @@ -11,7 +11,7 @@ fn main() { napi_build::setup(); } - #[cfg(feature = "py")] + #[cfg(any(feature = "py", feature = "py-noabi"))] { pyo3_build_config::add_extension_module_link_args(); } diff --git a/dist/py/pyproject.toml b/dist/py/pyproject.toml index 3b881ec..7556caf 100644 --- a/dist/py/pyproject.toml +++ b/dist/py/pyproject.toml @@ -2,7 +2,7 @@ name = "codemp" version = "0.7.0" description = "code multiplexer" -requires-python = ">= 3.8" +requires-python = ">=3.8" license = "GPL-3.0-only" keywords = ["codemp", "cooperative", "rust", "python"] authors = [ @@ -28,7 +28,7 @@ requires = ["maturin>=1.0,<2.0"] build-backend = "maturin" [tool.maturin] -features = ["py", "pyo3/extension-module"] +features = ["py"] manifest-path = "../../Cargo.toml" python-source = "src" diff --git a/src/api/change.rs b/src/api/change.rs index 9c04aad..40e601e 100644 --- a/src/api/change.rs +++ b/src/api/change.rs @@ -2,7 +2,7 @@ //! A high-level representation of a change within a given buffer. /// An editor-friendly representation of a text change in a given buffer. -/// +/// /// It's expressed with a range of characters and a string of content that should replace them, /// allowing representation of any combination of deletions, insertions or replacements. /// @@ -22,7 +22,7 @@ /// #[derive(Clone, Debug, Default)] #[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))] pub struct TextChange { /// 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 { /// Returns true if this [`TextChange`] deletes existing text. /// diff --git a/src/api/config.rs b/src/api/config.rs index 8a70a10..58cfd2f 100644 --- a/src/api/config.rs +++ b/src/api/config.rs @@ -10,7 +10,10 @@ /// http{tls?'s':''}://{host}:{port} #[derive(Clone, Debug)] #[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))] pub struct Config { /// user identifier used to register, possibly your email diff --git a/src/api/cursor.rs b/src/api/cursor.rs index 1016ca5..7c1e4b5 100644 --- a/src/api/cursor.rs +++ b/src/api/cursor.rs @@ -1,12 +1,12 @@ //! ### Cursor //! Represents the position of a remote user's cursor. -#[cfg(feature = "py")] +#[cfg(any(feature = "py", feature = "py-noabi"))] use pyo3::prelude::*; /// User cursor position in a buffer #[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 = "py", pyo3(crate = "reexported::pyo3"))] pub struct Cursor { diff --git a/src/api/event.rs b/src/api/event.rs index 26e87df..c4c73c5 100644 --- a/src/api/event.rs +++ b/src/api/event.rs @@ -4,7 +4,7 @@ use codemp_proto::workspace::workspace_event::Event as WorkspaceEventInner; /// Event in a [crate::Workspace]. #[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))] pub enum Event { /// Fired when the file tree changes. diff --git a/src/buffer/controller.rs b/src/buffer/controller.rs index a690b05..f1fdbbc 100644 --- a/src/buffer/controller.rs +++ b/src/buffer/controller.rs @@ -18,7 +18,7 @@ use super::worker::DeltaRequest; /// 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. #[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)] pub struct BufferController(pub(crate) Arc); diff --git a/src/client.rs b/src/client.rs index 4a586f8..2d4d03a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,15 +4,25 @@ use std::sync::Arc; use dashmap::DashMap; -use tonic::{service::interceptor::InterceptedService, transport::{Channel, Endpoint}}; - -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}, +use tonic::{ + service::interceptor::InterceptedService, + transport::{Channel, Endpoint}, }; -#[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::*; /// A `codemp` client handle. @@ -22,7 +32,7 @@ use pyo3::prelude::*; /// A new [`Client`] can be obtained with [`Client::connect`]. #[derive(Debug, Clone)] #[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); #[derive(Debug)] @@ -42,30 +52,37 @@ impl Client { let channel = Endpoint::from_shared(config.endpoint())?.connect().await?; let mut auth = AuthClient::new(channel.clone()); - let resp = auth.login(LoginRequest { - username: config.username.clone(), - password: config.password.clone(), - }) + let resp = auth + .login(LoginRequest { + username: config.username.clone(), + password: config.password.clone(), + }) .await? .into_inner(); let claims = InternallyMutable::new(resp.token); // TODO move this one into network.rs - let session = SessionClient::with_interceptor( - channel, network::SessionInterceptor(claims.channel()) - ); + let session = + SessionClient::with_interceptor(channel, network::SessionInterceptor(claims.channel())); Ok(Client(Arc::new(ClientInner { user: resp.user.into(), workspaces: DashMap::default(), - claims, auth, session, config + claims, + auth, + session, + config, }))) } /// Refresh session token. 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? .into_inner(); self.0.claims.set(new_token); @@ -74,25 +91,36 @@ impl Client { /// Attempt to create a new workspace with given name. pub async fn create_workspace(&self, name: impl AsRef) -> RemoteResult<()> { - self.0.session + self.0 + .session .clone() - .create_workspace(WorkspaceRequest { workspace: name.as_ref().to_string() }) + .create_workspace(WorkspaceRequest { + workspace: name.as_ref().to_string(), + }) .await?; Ok(()) } /// Delete an existing workspace if possible. pub async fn delete_workspace(&self, name: impl AsRef) -> RemoteResult<()> { - self.0.session + self.0 + .session .clone() - .delete_workspace(WorkspaceRequest { workspace: name.as_ref().to_string() }) + .delete_workspace(WorkspaceRequest { + workspace: name.as_ref().to_string(), + }) .await?; Ok(()) } /// Invite user with given username to the given workspace, if possible. - pub async fn invite_to_workspace(&self, workspace_name: impl AsRef, user_name: impl AsRef) -> RemoteResult<()> { - self.0.session + pub async fn invite_to_workspace( + &self, + workspace_name: impl AsRef, + user_name: impl AsRef, + ) -> RemoteResult<()> { + self.0 + .session .clone() .invite_to_workspace(InviteRequest { 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. pub async fn list_workspaces(&self, owned: bool, invited: bool) -> RemoteResult> { - let mut workspaces = self.0.session + let mut workspaces = self + .0 + .session .clone() .list_workspaces(Empty {}) .await? @@ -112,17 +142,25 @@ impl Client { let mut out = Vec::new(); - if owned { out.append(&mut workspaces.owned) } - if invited { out.append(&mut workspaces.invited) } + if owned { + out.append(&mut workspaces.owned) + } + if invited { + out.append(&mut workspaces.invited) + } Ok(out) } /// Join and return a [`Workspace`]. pub async fn join_workspace(&self, workspace: impl AsRef) -> ConnectionResult { - let token = self.0.session + let token = self + .0 + .session .clone() - .access_workspace(WorkspaceRequest { workspace: workspace.as_ref().to_string() }) + .access_workspace(WorkspaceRequest { + workspace: workspace.as_ref().to_string(), + }) .await? .into_inner(); diff --git a/src/cursor/controller.rs b/src/cursor/controller.rs index 014b1cf..8c2c8a4 100644 --- a/src/cursor/controller.rs +++ b/src/cursor/controller.rs @@ -5,14 +5,20 @@ use std::sync::Arc; use tokio::sync::{mpsc, oneshot, watch}; -use crate::{api::{controller::ControllerCallback, Controller, Cursor}, errors::ControllerResult}; -use codemp_proto::{cursor::{CursorPosition, RowCol}, files::BufferNode}; +use crate::{ + api::{controller::ControllerCallback, Controller, Cursor}, + errors::ControllerResult, +}; +use codemp_proto::{ + cursor::{CursorPosition, RowCol}, + files::BufferNode, +}; /// A [Controller] for asynchronously sending and receiving [Cursor] event. /// /// An unique [CursorController] exists for each active [crate::Workspace]. #[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)] pub struct CursorController(pub(crate) Arc); @@ -31,11 +37,23 @@ impl Controller for CursorController { if cursor.start > cursor.end { std::mem::swap(&mut cursor.start, &mut cursor.end); } - Ok(self.0.op.send(CursorPosition { - 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?) + Ok(self + .0 + .op + .send(CursorPosition { + 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> { diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index f9c4ae4..fa03449 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -1,20 +1,20 @@ //! # Foreign Function Interface //! `codemp` aims to be available as a library from as many programming languages as possible. //! To achieve this, we rely on Foreign Function Interface. -//! +//! //! ## JavaScript //! Our JavaScript glue is built with [`napi`](https://napi.rs). //! //! All async operations are handled on a separate tokio runtime, automatically managed by `napi`. //! Callbacks are safely scheduled to be called on the main loop thread. -//! +//! //! ## Python //! Our Python glue is built with [`PyO3`](https://pyo3.rs). //! //! All async operations return a `Promise`, which can we `.wait()`-ed to block and get the return //! value. The `Future` itself is run on a `tokio` runtime in a dedicated thread, which must be //! stared with `codemp.init()` before doing any async operations. -//! +//! //! ## Lua //! Our Lua glue is built with [`mlua`](https://github.com/mlua-rs/mlua). //! @@ -27,21 +27,21 @@ //! Note as Lua uses filename to locate entrypoint symbol, so shared object can't just have any name. //! Accepted filenames are `libcodemp.___`, `codemp.___`, `codemp_native.___`, `codemp_lua.___` (extension depends on your platform: `so` on linux, `dll` on windows, `dylib` on macos). //! Type hints are provided in `dist/lua/annotations.lua`, just include them in your language server: `---@module 'annotations'`. -//! +//! //! `codemp` is available as a rock on [LuaRocks](https://luarocks.org/modules/alemi/codemp), //! however LuaRocks compiles from source and will require having `cargo` installed. //! We provide pre-built binaries at [codemp.dev/releases/lua](https://codemp.dev/releases/lua/). //! **Please do not rely on this link, as our built binaries will likely move somewhere else soon!**. -//! +//! //! ## Java //! Our Java glue is built with [`jni`](https://github.com/jni-rs/jni-rs). //! //! Memory management is entirely delegated to the JVM's garbage collector. //! A more elegant solution than `Object.finalize()`, who is deprecated in newer Java versions, may be coming eventually. -//! +//! //! Exceptions coming from the native side have generally been made checked to imitate Rust's philosophy with `Result`. //! `JNIException`s are however unchecked: there is nothing you can do to recover from them, as they usually represent a severe error in the glue code. If they arise, it's probably a bug. -//! +//! /// java bindings, built with [jni] #[cfg(feature = "java")] @@ -56,5 +56,5 @@ pub mod lua; pub mod js; /// python bindings, built with [pyo3] -#[cfg(feature = "py")] +#[cfg(any(feature = "py", feature = "py-noabi"))] pub mod python; diff --git a/src/workspace.rs b/src/workspace.rs index b474121..a88f6b1 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -9,7 +9,7 @@ use crate::{ cursor::{self, worker::CursorWorker}, errors::{ConnectionResult, ControllerResult, RemoteResult}, ext::InternallyMutable, - network::Services + network::Services, }; use codemp_proto::{ @@ -33,7 +33,7 @@ use uuid::Uuid; use napi_derive::napi; #[derive(Debug, Clone)] -#[cfg_attr(feature = "py", pyo3::pyclass)] +#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)] #[cfg_attr(feature = "js", napi)] pub struct Workspace(Arc); @@ -59,7 +59,8 @@ impl Workspace { claims: tokio::sync::watch::Receiver, // TODO ughh receiving this ) -> ConnectionResult { 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 (tx, rx) = mpsc::channel(128); @@ -126,12 +127,12 @@ impl Workspace { let (tx, rx) = mpsc::channel(256); let mut req = tonic::Request::new(tokio_stream::wrappers::ReceiverStream::new(rx)); - req.metadata_mut() - .insert( - "buffer", - tonic::metadata::MetadataValue::try_from(credentials.token) - .map_err(|e| tonic::Status::internal(format!("failed representing token to string: {e}")))?, - ); + req.metadata_mut().insert( + "buffer", + tonic::metadata::MetadataValue::try_from(credentials.token).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 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). // #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120 pub fn filetree(&self, filter: Option<&str>, strict: bool) -> Vec { - self.0.filetree.iter() - .filter(|f| filter.map_or(true, |flt| { - if strict { - f.as_str() == flt - } else { - f.starts_with(flt) - } - })) + self.0 + .filetree + .iter() + .filter(|f| { + filter.map_or(true, |flt| { + if strict { + f.as_str() == flt + } else { + f.starts_with(flt) + } + }) + }) .map(|f| f.clone()) .collect() } @@ -315,9 +320,7 @@ impl Workspace { match ev { // user WorkspaceEventInner::Join(UserJoin { user }) => { - inner - .users - .insert(user.id.uuid(), user.into()); + inner.users.insert(user.id.uuid(), user.into()); } WorkspaceEventInner::Leave(UserLeave { user }) => { 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(feature = "py", derive(PartialEq))] +#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass(eq, eq_int))] +#[cfg_attr(any(feature = "py", feature = "py-noabi"), derive(PartialEq))] pub enum DetachResult { NotAttached, Detaching, From 5741eacc85d4c92250eb44fc2158768168319843 Mon Sep 17 00:00:00 2001 From: cschen Date: Thu, 19 Sep 2024 21:34:06 +0200 Subject: [PATCH 2/5] ci(python): testing the updated CIs to build both a generic windows wheel and a python3.8 one. --- .github/workflows/python.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 28a24f5..b45c375 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -9,6 +9,7 @@ on: push: branches: - stable + - fix-ci-python permissions: contents: read @@ -89,6 +90,11 @@ jobs: runs-on: ${{ matrix.platform.runner }} strategy: matrix: + versions: + - python: 3.8 + features: py-noabi + - python: 3.x + features: py platform: - runner: windows-latest target: x64 @@ -101,7 +107,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/setup-python@v5 with: - python-version: 3.x + python-version: ${{ matrix.versions.python }} architecture: ${{ matrix.platform.target }} - name: Build wheels uses: PyO3/maturin-action@v1 @@ -109,7 +115,7 @@ jobs: working-directory: dist/py target: ${{ matrix.platform.target }} container: 'off' - args: --release --out ./build + args: --release --out ./build --features ${{ matrix.versions.features }} sccache: 'true' - name: Upload wheels uses: actions/upload-artifact@v4 From 89bfd6b90c4725738b002fe6d9277d12dd6f9aaf Mon Sep 17 00:00:00 2001 From: cschen Date: Thu, 19 Sep 2024 21:37:38 +0200 Subject: [PATCH 3/5] fix(ci-python): don't overwrite current pypi wheels. use different version. --- dist/py/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/py/pyproject.toml b/dist/py/pyproject.toml index 7556caf..8275de7 100644 --- a/dist/py/pyproject.toml +++ b/dist/py/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codemp" -version = "0.7.0" +version = "0.0.1" description = "code multiplexer" requires-python = ">=3.8" license = "GPL-3.0-only" From aa74679bcca6342f8f31c90a5b0cfb95cd222efb Mon Sep 17 00:00:00 2001 From: cschen Date: Thu, 19 Sep 2024 21:51:23 +0200 Subject: [PATCH 4/5] ci(python): tentative n 2, forgot to differentiate the artefact names. --- .github/workflows/python.yml | 2 +- dist/py/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index b45c375..b30c10f 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -120,7 +120,7 @@ jobs: - name: Upload wheels uses: actions/upload-artifact@v4 with: - name: codemp-py-windows-${{ matrix.platform.target }} + name: codemp-${{ matrix.versions.features }}-windows-${{ matrix.platform.target }} path: dist/py/build macos: diff --git a/dist/py/pyproject.toml b/dist/py/pyproject.toml index 8275de7..b864fc6 100644 --- a/dist/py/pyproject.toml +++ b/dist/py/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codemp" -version = "0.0.1" +version = "0.7.1" description = "code multiplexer" requires-python = ">=3.8" license = "GPL-3.0-only" From 1851cf3f59ade8458faa2c92573cf0c62d8cf22b Mon Sep 17 00:00:00 2001 From: cschen Date: Fri, 20 Sep 2024 01:31:33 +0200 Subject: [PATCH 5/5] ci(python): revert to only running ci on stable. --- .github/workflows/python.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index b30c10f..8ad813f 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -9,7 +9,6 @@ on: push: branches: - stable - - fix-ci-python permissions: contents: read