Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build/
.venv/
__pycache__/
*.pyc
.git/
83 changes: 83 additions & 0 deletions .github/workflows/cmake-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: CMake Build

on:
pull_request:
branches: [master]

jobs:
build-python:
name: Python bindings
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4

- name: Install system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y libeigen3-dev nlohmann-json3-dev

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Configure
run: cmake --preset python -DRCQP_GENERATE_PYI=OFF

- name: Build
run: cmake --build --preset python -- -j$(nproc)

build-julia:
name: Julia bindings
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4

- name: Install system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y libeigen3-dev nlohmann-json3-dev

- name: Set up Julia
uses: julia-actions/setup-julia@v2
with:
version: "1"

- name: Install CxxWrap.jl
run: julia --startup-file=no -e 'using Pkg; Pkg.add("CxxWrap")'

- name: Configure
run: cmake --preset julia

- name: Build
run: cmake --build --preset julia -- -j$(nproc)

build-all:
name: Python + Julia bindings
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4

- name: Install system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y libeigen3-dev nlohmann-json3-dev

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Set up Julia
uses: julia-actions/setup-julia@v2
with:
version: "1"

- name: Install CxxWrap.jl
run: julia --startup-file=no -e 'using Pkg; Pkg.add("CxxWrap")'

- name: Configure
run: cmake --preset all -DRCQP_GENERATE_PYI=OFF

- name: Build
run: cmake --build --preset all -- -j$(nproc)
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.vscode
build/*
build/*
.venv/
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.9
193 changes: 146 additions & 47 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,67 +1,166 @@
cmake_minimum_required(VERSION 3.10.2)
project(rcqp)
cmake_minimum_required(VERSION 3.28) # 3.28 needed for EXCLUDE_FROM_ALL in FetchContent_Declare

project(rcqp LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# ---------------------------------------------------------------------------
# Build options
# ---------------------------------------------------------------------------
option(RCQP_BUILD_PYTHON "Build Python bindings" ON)
option(RCQP_BUILD_JULIA "Build Julia bindings" ON)
option(RCQP_GENERATE_PYI "Generate .pyi stubs via pybind11-stubgen" ON)

# ---------------------------------------------------------------------------
# Output directories (keeps build/ tidy)
# ---------------------------------------------------------------------------
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

# ---------------------------------------------------------------------------
# Dependencies
find_package(Eigen3 3.3 REQUIRED NO_MODULE) # provides Eigen3::Eigen
find_package(QDLDL)
# ---------------------------------------------------------------------------
include(FetchContent)

find_package(Eigen3 3.3 REQUIRED NO_MODULE)
find_package(nlohmann_json CONFIG REQUIRED)

if(NOT QDLDL_FOUND)
include(FetchContent)
# QDLDL – prefer a system install, otherwise fetch as static-only.
# EXCLUDE_FROM_ALL suppresses QDLDL's cmake --install rules so they don't
# interfere with pip install / scikit-build-core (requires CMake 3.28).
find_package(qdldl CONFIG QUIET)
if(NOT qdldl_FOUND)
set(QDLDL_BUILD_STATIC_LIB ON CACHE BOOL "" FORCE)
set(QDLDL_BUILD_SHARED_LIB OFF CACHE BOOL "" FORCE)
set(QDLDL_BUILD_DEMO_EXE OFF CACHE BOOL "" FORCE)
set(QDLDL_UNITTESTS OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
qdldl
GIT_REPOSITORY https://github.com/oxfordcontrol/QDLDL.git
GIT_TAG master
qdldl
GIT_REPOSITORY https://github.com/osqp/qdldl.git
GIT_TAG master
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(qdldl)
# If the fetched project provides a target, use it; otherwise adjust below.
endif()

# Create alias if needed for consistent naming
if(NOT TARGET qdldl::qdldl)
if(TARGET qdldl)
add_library(qdldl::qdldl ALIAS qdldl)
endif()
# Normalise QDLDL to a single interface target regardless of how it was found
add_library(rcqp_qdldl INTERFACE)
if(TARGET qdldl::qdldlstatic)
target_link_libraries(rcqp_qdldl INTERFACE qdldl::qdldlstatic)
elseif(TARGET qdldl::qdldl)
target_link_libraries(rcqp_qdldl INTERFACE qdldl::qdldl)
elseif(TARGET qdldlstatic)
target_link_libraries(rcqp_qdldl INTERFACE qdldlstatic)
elseif(TARGET qdldl)
target_link_libraries(rcqp_qdldl INTERFACE qdldl)
else()
message(FATAL_ERROR "Could not find a usable QDLDL target")
endif()

find_package(JlCxx)
get_target_property(JlCxx_location JlCxx::cxxwrap_julia LOCATION)
get_filename_component(JlCxx_location ${JlCxx_location} DIRECTORY)
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${JlCxx_location}")
# ---------------------------------------------------------------------------
# Core library (static → gets bundled into each wrapper, no rpath issues)
# ---------------------------------------------------------------------------
add_library(rcqp STATIC
src/solver.cpp
src/problem.cpp
src/workspace.cpp
src/filter.cpp
)
target_include_directories(rcqp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(rcqp PUBLIC Eigen3::Eigen nlohmann_json::nlohmann_json rcqp_qdldl)

message(STATUS "Found JlCxx at ${JlCxx_location}")
# ---------------------------------------------------------------------------
# Python bindings (cmake -DRCQP_BUILD_PYTHON=ON — default ON)
# ---------------------------------------------------------------------------
if(RCQP_BUILD_PYTHON)
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)

# Setup library
add_library(rcqp SHARED
${CMAKE_CURRENT_SOURCE_DIR}/src/solver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/problem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/workspace.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/filter.cpp
)
find_package(pybind11 CONFIG QUIET)
if(NOT pybind11_FOUND)
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG v2.13.6
)
FetchContent_MakeAvailable(pybind11)
endif()

target_include_directories(rcqp
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)
pybind11_add_module(rcqp_python MODULE bindings/python_bindings.cpp)
target_link_libraries(rcqp_python PRIVATE rcqp)
set_target_properties(rcqp_python PROPERTIES
OUTPUT_NAME rcqp
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/python
)

# .pyi stub generation (for VSCode / Pylance autocomplete)
if(RCQP_GENERATE_PYI)
find_program(PYBIND11_STUBGEN pybind11-stubgen)
if(PYBIND11_STUBGEN)
add_custom_command(TARGET rcqp_python POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_SOURCE_DIR}/typings
COMMAND ${CMAKE_COMMAND} -E env
PYTHONPATH=${CMAKE_BINARY_DIR}/python
${PYBIND11_STUBGEN} rcqp
--output-dir ${CMAKE_SOURCE_DIR}/typings
--numpy-array-remove-parameters
--ignore-unresolved-names "^(m|n)$"
COMMENT "Generating rcqp.pyi stubs"
VERBATIM
)
else()
message(WARNING
"pybind11-stubgen not found — .pyi stubs won't be regenerated. "
"Run: pip install pybind11-stubgen")
endif()
endif()

install(TARGETS rcqp_python LIBRARY DESTINATION .)
endif()

target_compile_features(rcqp PUBLIC cxx_std_17)

# Wrapper library for Julia
add_library(rcqp_wrapper SHARED wrapper.cpp)
target_link_libraries(rcqp_wrapper
PRIVATE
rcqp
JlCxx::cxxwrap_julia)

# Link dependencies
target_link_libraries(rcqp
PUBLIC
Eigen3::Eigen
qdldl
nlohmann_json::nlohmann_json
)
# ---------------------------------------------------------------------------
# Julia bindings (cmake -DRCQP_BUILD_JULIA=ON)
# ---------------------------------------------------------------------------
if(RCQP_BUILD_JULIA)
# Auto-detect JlCxx prefix by asking Julia where CxxWrap installed it.
# This works on any machine that has Julia + CxxWrap.jl without any
# hardcoded paths. Users can still override with -DJlCxx_DIR=<path>.
if(NOT DEFINED JlCxx_DIR)
find_program(JULIA_EXECUTABLE julia)
if(JULIA_EXECUTABLE)
execute_process(
COMMAND ${JULIA_EXECUTABLE} --startup-file=no -e
"using CxxWrap; print(CxxWrap.prefix_path())"
OUTPUT_VARIABLE _jlcxx_prefix
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(_jlcxx_prefix)
set(JlCxx_DIR "${_jlcxx_prefix}/lib/cmake/JlCxx"
CACHE PATH "Path to JlCxxConfig.cmake (auto-detected from Julia)")
message(STATUS "Auto-detected JlCxx_DIR: ${JlCxx_DIR}")
else()
message(FATAL_ERROR
"Could not detect JlCxx prefix from Julia.\n"
"Make sure CxxWrap.jl is installed: julia -e 'using Pkg; Pkg.add(\"CxxWrap\")'\n"
"Or set JlCxx_DIR manually: cmake -DJlCxx_DIR=<path/to/cmake/JlCxx>")
endif()
else()
message(FATAL_ERROR
"julia executable not found.\n"
"Install Julia from https://julialang.org/downloads or set JlCxx_DIR manually.")
endif()
endif()

find_package(JlCxx REQUIRED)
add_library(rcqp_julia SHARED bindings/julia_bindings.cpp)
target_link_libraries(rcqp_julia PRIVATE rcqp JlCxx::cxxwrap_julia)
set_target_properties(rcqp_julia PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)
endif()
42 changes: 42 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"version": 3,
"configurePresets": [
{
"name": "python",
"displayName": "Python bindings only",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"RCQP_BUILD_PYTHON": "ON",
"RCQP_BUILD_JULIA": "OFF",
"RCQP_GENERATE_PYI": "ON"
}
},
{
"name": "julia",
"displayName": "Julia bindings only (requires Julia + CxxWrap.jl)",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"RCQP_BUILD_PYTHON": "OFF",
"RCQP_BUILD_JULIA": "ON"
}
},
{
"name": "all",
"displayName": "Python + Julia bindings (requires Julia + CxxWrap.jl)",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"RCQP_BUILD_PYTHON": "ON",
"RCQP_BUILD_JULIA": "ON",
"RCQP_GENERATE_PYI": "ON"
}
}
],
"buildPresets": [
{ "name": "python", "configurePreset": "python" },
{ "name": "julia", "configurePreset": "julia" },
{ "name": "all", "configurePreset": "all" }
]
}
Loading
Loading