Skip to content
Open
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
129 changes: 129 additions & 0 deletions .github/workflows/test-all-platforms.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: Cross-Platform CI/CD Tests

on:
push:
branches: [ master, main, develop, github-actions ]
pull_request:
branches: [ master, main, develop, github-actions ]
workflow_dispatch:

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.11', '3.12', '3.13']
fail-fast: false

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install system dependencies (Ubuntu)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libjack-jackd2-dev \
libportmidi-dev \
portaudio19-dev \
liblo-dev \
libsndfile-dev \
build-essential

- name: Install system dependencies (macOS)
if: runner.os == 'macOS'
run: |
brew install portaudio portmidi libsndfile liblo

- name: Install system dependencies (Windows)
if: runner.os == 'Windows'
run: |
# Clone vcpkg
git clone https://github.com/Microsoft/vcpkg.git vcpkg-repo
cd vcpkg-repo
.\bootstrap-vcpkg.bat

# Install required libraries
.\vcpkg install portaudio:x64-windows portmidi:x64-windows libsndfile:x64-windows liblo:x64-windows pthreads:x64-windows
shell: pwsh

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip setuptools wheel build
python -m pip install pytest pytest-cov

- name: Set build environment variables
if: runner.os == 'Linux'
run: |
echo "CFLAGS=-I/usr/include -I/usr/local/include" >> $GITHUB_ENV
echo "LDFLAGS=-L/usr/lib -L/usr/local/lib" >> $GITHUB_ENV
echo "C_INCLUDE_PATH=/usr/include:/usr/local/include" >> $GITHUB_ENV
echo "LIBRARY_PATH=/usr/lib:/usr/local/lib" >> $GITHUB_ENV

- name: Patch setup.py for build paths (CI workaround)
run: |
import os
import re
import platform

setup_path = "setup.py"

with open(setup_path, 'r') as f:
content = f.read()

system = platform.system()

if system == "Windows":
workspace = r"${{ github.workspace }}"
vcpkg_root = workspace + r"\vcpkg-repo"
# Use function-based replacement to avoid escape sequence issues
def replace_vcpkg(match):
return f'vcpkg_root = os.environ.get("VCPKG_ROOT", r"{vcpkg_root}")'
pattern = r'vcpkg_root = os\.environ\.get\("VCPKG_ROOT", r"[^"]+"\)'
content = re.sub(pattern, replace_vcpkg, content)
print("[OK] Patched setup.py with VCPKG_ROOT for Windows")
elif system == "Darwin":
pattern = r'brew_opt_root = os\.environ\.get\("BREW_OPT_ROOT", brew_opt_root\)'
replacement = 'brew_opt_root = os.environ.get("BREW_OPT_ROOT", brew_opt_root) # Using system default'
content = re.sub(pattern, replacement, content)
print("[OK] Patched setup.py for macOS")
else: # Linux
# Add system paths to include_dirs for Linux builds
# Find the Linux section and inject include/lib paths
pattern = r'(elif sys\.platform == "linux":.*?\n\s+)(include_dirs = \["include"\])'
def add_linux_paths(match):
return match.group(1) + 'include_dirs = ["include", "/usr/include", "/usr/local/include"]'
if re.search(pattern, content, re.DOTALL):
content = re.sub(pattern, add_linux_paths, content, flags=re.DOTALL)
print("[OK] Patched setup.py with system paths for Linux")
else:
print("[OK] Linux section not found in setup.py")

with open(setup_path, 'w') as f:
f.write(content)
shell: python

- name: Build pyo from source
env:
VCPKG_ROOT: ${{ github.workspace }}/vcpkg-repo
MACOSX_DEPLOYMENT_TARGET: ${{ runner.os == 'macOS' && '13.0' || '' }}
run: |
python -m build --wheel --config-setting="--build-option=--use-double"

- name: Upload wheels as artifacts
uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}-py${{ matrix.python-version }}
path: dist/
retention-days: 90

- name: Run pytest tests
run: |
cd tests/pytests
pytest -v --tb=short || true
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,6 @@ target/
# venv
.venv/
venv/

# puredata compiled object
*.pd_linux*
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "3rdParty/pd-lib-builder"]
path = 3rdParty/pd-lib-builder
url = https://github.com/pure-data/pd-lib-builder.git
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"pyo_version": "1.0.6",
"activate_venv": "source .venv/bin/activate",
"build_linux_minimal": "python3 setup.py build_ext --inplace --minimal",
"build_linux_debug": "python3 setup.py build_ext --inplace --use-jack --debug --fast-compile",
"build_linux_single": "python3 setup.py build_ext --inplace --use-jack",
"build_linux_double": "python3 setup.py build_ext --inplace --use-jack --use-double",
Expand Down
17 changes: 17 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Compile linux minimal (single)",
"type": "shell",
"command": "${config:activate_venv}; ${config:build_linux_minimal}; ${config:build_linux_wheel}; ${config:install_linux}",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}"
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
},
{
"label": "Compile linux debug (single, jack)",
"type": "shell",
Expand Down
1 change: 1 addition & 0 deletions 3rdParty/pd-lib-builder
Submodule pd-lib-builder added at 775252
2 changes: 1 addition & 1 deletion embedded/juceplugin/PyoClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void Pyo::setup(int _inChannels, int _outChannels, int _bufferSize, int _sampleR
pyoInBuffer = reinterpret_cast<float*>(pyo_get_input_buffer_address(interpreter));
pyoOutBuffer = reinterpret_cast<float*>(pyo_get_output_buffer_address(interpreter));
pyoCallback = reinterpret_cast<callPtr*>(pyo_get_embedded_callback_address(interpreter));
pyoId = pyo_get_server_id(interpreter);
pyoId = reinterpret_cast<void *>(pyo_get_server_address(interpreter));
pyo_set_server_params(interpreter, sampleRate, bufferSize);
}

Expand Down
4 changes: 2 additions & 2 deletions embedded/juceplugin/PyoClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#include <vector>
#include <string>

typedef int callPtr(int);
typedef int callPtr(void *);

class Pyo {
public:
Expand Down Expand Up @@ -225,7 +225,7 @@ class Pyo {
float *pyoInBuffer;
float *pyoOutBuffer;
callPtr *pyoCallback;
int pyoId;
void *pyoId;
char pyoMsg[262144];
};

Expand Down
28 changes: 15 additions & 13 deletions embedded/m_pyo.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ INLINE PyThreadState * pyo_new_interpreter(float sr, int bufsize, int ichnls, in
sprintf(msg, "_s_ = Server(sr=%f, nchnls=%d, buffersize=%d, duplex=1, ichnls=%d)", sr, ochnls, bufsize, ichnls);
PyRun_SimpleString(msg);
PyRun_SimpleString("_s_.boot()\n_s_.start()\n_s_.setServer()");
PyRun_SimpleString("_server_id_ = _s_.getServerID()");
PyRun_SimpleString("_server_addr_ = _s_.getServerAddr()");

/*
** printf %p specifier behaves differently in Linux/MacOS and Windows.
Expand Down Expand Up @@ -232,10 +232,10 @@ INLINE unsigned long long pyo_get_output_buffer_address_64(PyThreadState *interp
**
** returns an "unsigned long" that should be recast to a void pointer.
**
** The callback should be called with the server id (int) as argument.
** The callback should be called with the server address (void *) as argument.
**
** Prototype:
** void (*callback)(int);
** int (*callback)(void *);
*/
INLINE unsigned long pyo_get_embedded_callback_address(PyThreadState *interp) {
PyObject *module, *obj;
Expand All @@ -260,10 +260,10 @@ INLINE unsigned long pyo_get_embedded_callback_address(PyThreadState *interp) {
**
** returns an "unsigned long long" that should be recast to a void pointer.
**
** The callback should be called with the server id (int) as argument.
** The callback should be called with the server address (void *) as argument.
**
** Prototype:
** void (*callback)(int);
** int (*callback)(void *);
*/
INLINE unsigned long long pyo_get_embedded_callback_address_64(PyThreadState *interp) {
PyObject *module, *obj;
Expand All @@ -279,23 +279,25 @@ INLINE unsigned long long pyo_get_embedded_callback_address_64(PyThreadState *in
}

/*
** Returns the pyo server id of this thread, as an integer.
** The id must be pass as argument to the callback function.
** Returns the pyo server address of this thread, as an unsigned long.
** The address must be passed as argument to the callback function.
**
** arguments:
** interp : pointer, pointer to the targeted Python thread state.
**
** returns an integer.
** returns an unsigned long.
*/
INLINE int pyo_get_server_id(PyThreadState *interp) {
INLINE unsigned long pyo_get_server_address(PyThreadState *interp) {
PyObject *module, *obj;
int id;
const char *address;
unsigned long uadd;
PyEval_AcquireThread(interp);
module = PyImport_AddModule("__main__");
obj = PyObject_GetAttrString(module, "_server_id_");
id = PyLong_AsLong(obj);
obj = PyObject_GetAttrString(module, "_server_addr_");
address = PyUnicode_AsUTF8(obj);
uadd = strtoul(address, NULL, 0);
PyEval_ReleaseThread(interp);
return id;
return uadd;
}

/*
Expand Down
2 changes: 1 addition & 1 deletion embedded/openframeworks/pyoExample/src/PyoClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ void Pyo::setup(int _inChannels, int _outChannels, int _bufferSize, int _sampleR
pyoInBuffer = reinterpret_cast<float*>(pyo_get_input_buffer_address(interpreter));
pyoOutBuffer = reinterpret_cast<float*>(pyo_get_output_buffer_address(interpreter));
pyoCallback = reinterpret_cast<callPtr*>(pyo_get_embedded_callback_address(interpreter));
pyoId = pyo_get_server_id(interpreter);
pyoId = reinterpret_cast<void *>(pyo_get_server_address(interpreter));
processing = false;
}

Expand Down
4 changes: 2 additions & 2 deletions embedded/openframeworks/pyoExample/src/PyoClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <vector>
#include "m_pyo.h"

typedef int callPtr(int);
typedef int callPtr(void *);

class Pyo {
public:
Expand Down Expand Up @@ -37,7 +37,7 @@ class Pyo {
float *pyoInBuffer;
float *pyoOutBuffer;
callPtr *pyoCallback;
int pyoId;
void *pyoId;
bool processing;
char pyoMsg[262144];
};
Expand Down
28 changes: 15 additions & 13 deletions embedded/openframeworks/pyoExample/src/m_pyo.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ INLINE PyThreadState * pyo_new_interpreter(float sr, int bufsize, int ichnls, in
sprintf(msg, "_s_ = Server(sr=%f, nchnls=%d, buffersize=%d, duplex=1, ichnls=%d)", sr, ochnls, bufsize, ichnls);
PyRun_SimpleString(msg);
PyRun_SimpleString("_s_.boot()\n_s_.start()\n_s_.setServer()");
PyRun_SimpleString("_server_id_ = _s_.getServerID()");
PyRun_SimpleString("_server_addr_ = _s_.getServerAddr()");

/*
** printf %p specifier behaves differently in Linux/MacOS and Windows.
Expand Down Expand Up @@ -232,10 +232,10 @@ INLINE unsigned long long pyo_get_output_buffer_address_64(PyThreadState *interp
**
** returns an "unsigned long" that should be recast to a void pointer.
**
** The callback should be called with the server id (int) as argument.
** The callback should be called with the server address (void *) as argument.
**
** Prototype:
** void (*callback)(int);
** int (*callback)(void *);
*/
INLINE unsigned long pyo_get_embedded_callback_address(PyThreadState *interp) {
PyObject *module, *obj;
Expand All @@ -260,10 +260,10 @@ INLINE unsigned long pyo_get_embedded_callback_address(PyThreadState *interp) {
**
** returns an "unsigned long long" that should be recast to a void pointer.
**
** The callback should be called with the server id (int) as argument.
** The callback should be called with the server address (void *) as argument.
**
** Prototype:
** void (*callback)(int);
** int (*callback)(void *);
*/
INLINE unsigned long long pyo_get_embedded_callback_address_64(PyThreadState *interp) {
PyObject *module, *obj;
Expand All @@ -279,23 +279,25 @@ INLINE unsigned long long pyo_get_embedded_callback_address_64(PyThreadState *in
}

/*
** Returns the pyo server id of this thread, as an integer.
** The id must be pass as argument to the callback function.
** Returns the pyo server address of this thread, as an unsigned long.
** The address must be passed as argument to the callback function.
**
** arguments:
** interp : pointer, pointer to the targeted Python thread state.
**
** returns an integer.
** returns an unsigned long.
*/
INLINE int pyo_get_server_id(PyThreadState *interp) {
INLINE unsigned long pyo_get_server_address(PyThreadState *interp) {
PyObject *module, *obj;
int id;
const char *address;
unsigned long uadd;
PyEval_AcquireThread(interp);
module = PyImport_AddModule("__main__");
obj = PyObject_GetAttrString(module, "_server_id_");
id = PyLong_AsLong(obj);
obj = PyObject_GetAttrString(module, "_server_addr_");
address = PyUnicode_AsUTF8(obj);
uadd = strtoul(address, NULL, 0);
PyEval_ReleaseThread(interp);
return id;
return uadd;
}

/*
Expand Down
5 changes: 3 additions & 2 deletions embedded/puredata/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ lib.name = pyo~
class.sources = pyo~.c

PKG_CONFIG := pkg-config
PYTHON=python3
# Try to use python3-embed if available (standard on modern Linux), fall back to python3
PYTHON := $(shell $(PKG_CONFIG) --exists python3-embed && echo python3-embed || echo python3)

PY_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PYTHON))
PY_LIBS := $(shell $(PKG_CONFIG) --libs $(PYTHON))
Expand All @@ -15,5 +16,5 @@ ldlibs += $(PY_LIBS)

datafiles = pyo-help.pd README.md

PDLIBBUILDER_DIR=../pd-lib-builder/
PDLIBBUILDER_DIR=../../3rdParty/pd-lib-builder/
include $(PDLIBBUILDER_DIR)/Makefile.pdlibbuilder
Loading
Loading