Skip to content
Merged
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
33 changes: 27 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ jobs:
with:
python-version: ${{ matrix.python }}

- name: Sync Basilisk artifacts
- name: Sync artifacts
run: python tools/sync_all.py

- name: Build wheel
run: pip wheel . --no-build-isolation --no-deps -w dist -v
run: |
pip install build
python -m build --wheel -o dist

- uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -98,12 +100,32 @@ jobs:
name: wheel-${{ matrix.os }}-py${{ matrix.python }}
path: dist

- name: Install bsk-sdk wheel
run: pip install dist/*.whl
- name: Install bsk-sdk wheel and dependencies
# Pin swig to the same version used to build the bsk wheel (4.3.1)
# so the plugin's SWIG runtime type tables are ABI-compatible.
run: pip install dist/*.whl numpy bsk "swig==4.3.1"
shell: bash

- name: Debug SWIG versions
run: |
python -c "import swig, os, pathlib, subprocess; exe='swig.exe' if os.name=='nt' else 'swig'; p=pathlib.Path(swig.BIN_DIR, exe); ver=subprocess.check_output([str(p),'-version'],text=True).splitlines()[1]; d=pathlib.Path(swig.SWIG_SHARE_DIR, swig.__version__); print('pip swig exe: ', p); print('pip swig dir: ', d); print('pip swig version:', ver)"
python -c "from Basilisk.simulation import exponentialAtmosphere; import inspect, pathlib; py=pathlib.Path(inspect.getfile(exponentialAtmosphere)); print('bsk swig version:', py.read_text().splitlines()[1])"
shell: bash

- name: Build example plugin wheel
run: pip wheel examples/custom-atm-plugin --no-build-isolation --no-deps -w plugin-dist -v
run: |
pip install build scikit-build-core
# Resolve pip-installed swig paths so CMake finds the right binary AND
# lib dir (SWIG_DIR). Without SWIG_DIR, FindSWIG fails because the pip
# binary has no compiled-in lib path when called directly by cmake.
SWIG_EXE=$(python -c "import swig, os, pathlib; exe='swig.exe' if os.name=='nt' else 'swig'; print(pathlib.Path(swig.BIN_DIR, exe).as_posix())")
SWIG_DIR=$(python -c "import swig, pathlib; print(pathlib.Path(swig.SWIG_SHARE_DIR, swig.__version__).as_posix())")
export SWIG_LIB="$SWIG_DIR"
python -m build --wheel --no-isolation \
-C cmake.define.SWIG_EXECUTABLE="$SWIG_EXE" \
-C cmake.define.SWIG_DIR="$SWIG_DIR" \
-o plugin-dist examples/custom-atm-plugin
shell: bash

- uses: actions/upload-artifact@v4
with:
Expand All @@ -116,5 +138,4 @@ jobs:
shell: bash

- name: Run example plugin tests
# Tests skip automatically if Basilisk is not installed.
run: pytest examples/custom-atm-plugin/test_atm_plugin.py -v
1 change: 1 addition & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[submodule "external/basilisk"]
path = external/basilisk
url = https://github.com/AVSLab/basilisk.git
branch = v2.9.1
19 changes: 19 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ if(NOT Eigen3_FOUND)
message(STATUS "bsk-sdk: Eigen3 not found locally — downloaded via FetchContent")
endif()

# Bundle Eigen3 headers into the wheel so downstream consumers are self-contained.
# Determine the directory that contains the Eigen/ subfolder.
if(DEFINED eigen3_SOURCE_DIR)
set(_bsk_eigen3_src "${eigen3_SOURCE_DIR}")
else()
get_target_property(_bsk_eigen3_incdirs Eigen3::Eigen INTERFACE_INCLUDE_DIRECTORIES)
list(GET _bsk_eigen3_incdirs 0 _bsk_eigen3_src)
endif()

install(
DIRECTORY "${_bsk_eigen3_src}/Eigen"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/eigen3"
)
install(
DIRECTORY "${_bsk_eigen3_src}/unsupported"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/eigen3"
OPTIONAL
)

# Header-only SDK target
add_library(bsk_sdk_headers INTERFACE)
add_library(bsk::sdk_headers ALIAS bsk_sdk_headers)
Expand Down
8 changes: 8 additions & 0 deletions cmake/bsk-sdkConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ set(BSK_SDK_RUNTIME_MIN_DIR "${BSK_SDK_PKG_DIR}/runtime_min" CACHE PATH "
set(BSK_SDK_SWIG_DIR "${BSK_SDK_PKG_DIR}/swig" CACHE PATH "bsk-sdk swig dir")
set(BSK_SDK_TOOLS_DIR "${BSK_SDK_PKG_DIR}/tools" CACHE PATH "bsk-sdk tools dir")

# Provide Eigen3::Eigen from bundled headers so plugins are self-contained.
if(NOT TARGET Eigen3::Eigen)
add_library(Eigen3::Eigen IMPORTED INTERFACE GLOBAL)
set_target_properties(Eigen3::Eigen PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${BSK_SDK_INCLUDE_DIR}/eigen3"
)
endif()

include("${_bsk_sdk_config_dir}/bsk-sdkTargets.cmake")

function(_bsk_sdk_make_alias_if_needed alias_name real_name)
Expand Down
70 changes: 68 additions & 2 deletions cmake/bsk_add_swig_module.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,63 @@ function(_bsk_resolve_basilisk_libs out_var)
set(${out_var} "${_libs}" PARENT_SCOPE)
endfunction()

function(_bsk_find_working_swig out_var)
set(_swig_candidates "")

if(DEFINED SWIG_EXECUTABLE AND SWIG_EXECUTABLE)
list(APPEND _swig_candidates "${SWIG_EXECUTABLE}")
endif()

find_program(_swig_on_path NAMES swig)
if(_swig_on_path)
list(APPEND _swig_candidates "${_swig_on_path}")
endif()

execute_process(
COMMAND brew --prefix swig
OUTPUT_VARIABLE _brew_swig_prefix
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE _brew_swig_res
)
if(_brew_swig_res EQUAL 0 AND EXISTS "${_brew_swig_prefix}/bin/swig")
list(APPEND _swig_candidates "${_brew_swig_prefix}/bin/swig")
endif()

file(GLOB _cellar_swigs "/opt/homebrew/Cellar/swig/*/bin/swig")
list(SORT _cellar_swigs ORDER DESCENDING)
if(_cellar_swigs)
list(APPEND _swig_candidates ${_cellar_swigs})
endif()

list(REMOVE_DUPLICATES _swig_candidates)

foreach(_cand IN LISTS _swig_candidates)
if(NOT EXISTS "${_cand}")
continue()
endif()

execute_process(
COMMAND "${_cand}" -version
OUTPUT_VARIABLE _swig_stdout
ERROR_VARIABLE _swig_stderr
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _swig_res
)

if(_swig_res EQUAL 0)
string(CONCAT _swig_text "${_swig_stdout}" "\n" "${_swig_stderr}")
if(_swig_text MATCHES "SWIG Version")
set(${out_var} "${_cand}" PARENT_SCOPE)
return()
endif()
endif()
endforeach()

set(${out_var} "" PARENT_SCOPE)
endfunction()

function(bsk_add_swig_module)
set(oneValueArgs TARGET INTERFACE OUTPUT_DIR)
set(multiValueArgs SOURCES INCLUDE_DIRS SWIG_INCLUDE_DIRS LINK_LIBS DEPENDS)
Expand All @@ -74,15 +131,24 @@ function(bsk_add_swig_module)
set(BSK_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
endif()

_bsk_find_working_swig(_bsk_working_swig)
if(_bsk_working_swig)
set(SWIG_EXECUTABLE "${_bsk_working_swig}" CACHE FILEPATH "Working SWIG executable" FORCE)
endif()

find_package(SWIG REQUIRED COMPONENTS python)
include(${SWIG_USE_FILE})

find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module NumPy)
find_package(Eigen3 CONFIG REQUIRED)

if(NOT BSK_LINK_LIBS)
_bsk_resolve_basilisk_libs(_bsk_libs)
set(BSK_LINK_LIBS "${_bsk_libs}")
if(TARGET bsk::arch_min)
set(BSK_LINK_LIBS "bsk::arch_min")
else()
_bsk_resolve_basilisk_libs(_bsk_libs)
set(BSK_LINK_LIBS "${_bsk_libs}")
endif()
endif()

_bsk_collect_swig_flags(_swig_flags)
Expand Down
8 changes: 6 additions & 2 deletions cmake/bsk_generate_messages.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,12 @@ function(bsk_generate_messages)
find_package(Eigen3 CONFIG REQUIRED)

if(NOT BSK_TARGET_LINK_LIBS)
_bsk_resolve_basilisk_libs(_bsk_libs)
set(BSK_TARGET_LINK_LIBS "${_bsk_libs}")
if(TARGET bsk::arch_min)
set(BSK_TARGET_LINK_LIBS "bsk::arch_min")
else()
_bsk_resolve_basilisk_libs(_bsk_libs)
set(BSK_TARGET_LINK_LIBS "${_bsk_libs}")
endif()
endif()


Expand Down
78 changes: 76 additions & 2 deletions examples/custom-atm-plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
cmake_minimum_required(VERSION 3.18)
project(bsk_plugin_exponential_atmosphere LANGUAGES CXX)
project(bsk_plugin_exponential_atmosphere LANGUAGES C CXX)

find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module NumPy)

# Force SWIG invocation through the active Python environment to avoid picking
# a system SWIG launcher bound to a different Python/runtime context.
execute_process(
COMMAND "${Python3_EXECUTABLE}" -c "import swig; print(swig.__version__, end='')"
OUTPUT_VARIABLE py_swig_version
RESULT_VARIABLE py_swig_rc
)
if(py_swig_rc EQUAL 0)
set(_swig_wrapper "${CMAKE_CURRENT_BINARY_DIR}/swig_from_python")
file(WRITE "${_swig_wrapper}" "#!/bin/sh\nexec \"${Python3_EXECUTABLE}\" -m swig \"$@\"\n")
execute_process(COMMAND chmod +x "${_swig_wrapper}")
set(SWIG_EXECUTABLE "${_swig_wrapper}" CACHE FILEPATH "SWIG executable" FORCE)
message(STATUS "Using Python SWIG ${py_swig_version} via ${SWIG_EXECUTABLE}")
endif()

# Locate bsk-sdk's CMake config dir from the active Python env
execute_process(
COMMAND "${Python3_EXECUTABLE}" -c "import bsk_sdk; print(bsk_sdk.cmake_config_dir(), end='')"
Expand All @@ -12,10 +27,45 @@ execute_process(
if(NOT rc EQUAL 0 OR bsk_sdk_dir STREQUAL "")
message(FATAL_ERROR "bsk-sdk not found (is it installed in this Python environment?)")
endif()
file(TO_CMAKE_PATH "${bsk_sdk_dir}" bsk_sdk_dir)

set(bsk-sdk_DIR "${bsk_sdk_dir}")
find_package(bsk-sdk CONFIG REQUIRED)

execute_process(
COMMAND "${Python3_EXECUTABLE}" -c "import bsk_sdk; print(bsk_sdk.package_root(), end='')"
OUTPUT_VARIABLE bsk_sdk_pkg_dir
RESULT_VARIABLE bsk_sdk_pkg_rc
)
if(NOT bsk_sdk_pkg_rc EQUAL 0 OR bsk_sdk_pkg_dir STREQUAL "")
message(FATAL_ERROR "Could not resolve bsk_sdk package_root()")
endif()
file(TO_CMAKE_PATH "${bsk_sdk_pkg_dir}" bsk_sdk_pkg_dir)

# Resolve Basilisk runtime libraries from the active Python environment.
# The plugin must link against the same Basilisk libs that back
# Basilisk.utilities.SimulationBaseClass to keep SysModel C++ type identity
# consistent across module boundaries.
execute_process(
COMMAND "${Python3_EXECUTABLE}" -c "import Basilisk, pathlib; print(pathlib.Path(Basilisk.__file__).resolve().parent, end='')"
OUTPUT_VARIABLE basilisk_pkg_dir
RESULT_VARIABLE basilisk_rc
)
if(NOT basilisk_rc EQUAL 0 OR basilisk_pkg_dir STREQUAL "")
message(FATAL_ERROR "Basilisk package not found in this Python environment")
endif()
file(TO_CMAKE_PATH "${basilisk_pkg_dir}" basilisk_pkg_dir)

set(_basilisk_lib_names architectureLib ArchitectureUtilities cMsgCInterface)
set(BASILISK_RUNTIME_LIBS "")
foreach(_libname IN LISTS _basilisk_lib_names)
find_library(_libpath NAMES ${_libname} PATHS "${basilisk_pkg_dir}" NO_DEFAULT_PATH)
if(NOT _libpath)
message(FATAL_ERROR "Basilisk runtime library '${_libname}' not found under ${basilisk_pkg_dir}")
endif()
list(APPEND BASILISK_RUNTIME_LIBS "${_libpath}")
endforeach()

# Where scikit-build-core wants wheel outputs
set(PLUGIN_PKG_DIR "${SKBUILD_PLATLIB_DIR}/custom_atm")

Expand All @@ -27,12 +77,36 @@ file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp"
)

# AtmosphereBase implementation is required at link/load time for the custom
# model subclass. Prefer the SDK-shipped runtime source to avoid depending on
# repository-local external/basilisk checkout structure in CI.
set(_atm_base_impl "${bsk_sdk_pkg_dir}/runtime_min/atmosphereBase.cpp")
if(NOT EXISTS "${_atm_base_impl}")
set(_atm_base_impl
"${CMAKE_CURRENT_SOURCE_DIR}/../../src/bsk_sdk/runtime_min/atmosphereBase.cpp"
)
endif()
if(EXISTS "${_atm_base_impl}")
list(APPEND PLUGIN_SOURCES "${_atm_base_impl}")
else()
message(FATAL_ERROR "atmosphereBase.cpp not found in bsk_sdk runtime_min")
endif()

set(_lin_alg_impl
"${CMAKE_CURRENT_SOURCE_DIR}/../../external/basilisk/src/architecture/utilities/linearAlgebra.c"
)
if(EXISTS "${_lin_alg_impl}")
set_source_files_properties("${_lin_alg_impl}" PROPERTIES LANGUAGE C)
list(APPEND PLUGIN_SOURCES "${_lin_alg_impl}")
endif()

# Build the SWIG module
bsk_add_swig_module(
TARGET customExponentialAtmosphere
INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/swig/customExponentialAtmosphere.i"
SOURCES ${PLUGIN_SOURCES}
INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/src"
LINK_LIBS ${BASILISK_RUNTIME_LIBS} bsk::arch_min
OUTPUT_DIR "${PLUGIN_PKG_DIR}"
)

Expand All @@ -49,4 +123,4 @@ bsk_generate_messages(
)

# Link against SDK-provided Basilisk minimal libs (if your plugin needs them)
target_link_libraries(customExponentialAtmosphere PRIVATE bsk::runtime_min bsk::arch_min)

7 changes: 6 additions & 1 deletion examples/custom-atm-plugin/custom_atm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@

import sys
from Basilisk.architecture import cSysModel as _cSysModel
from . import customExponentialAtmosphere

# IMPORTANT: register cSysModel under its short name *before* importing the
# C extension below. The SWIG-generated wrapper does `import cSysModel` at
# class-definition time (module load), not at instantiation time. Without
# this line the import fails with a confusing ModuleNotFoundError.
sys.modules.setdefault("cSysModel", _cSysModel)

from . import customExponentialAtmosphere

__all__ = ["customExponentialAtmosphere"]
5 changes: 4 additions & 1 deletion examples/custom-atm-plugin/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["scikit-build-core>=0.9.3", "bsk-sdk>=1.0.0"]
requires = ["scikit-build-core>=0.9.3", "numpy>=1.24", "swig==4.3.1"]
build-backend = "scikit_build_core.build"

[project]
Expand All @@ -8,3 +8,6 @@ version = "0.1.0"

[tool.scikit-build]
wheel.packages = ["custom_atm"]

[tool.pytest.ini_options]
addopts = ["--import-mode=importlib"]
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#include "simulation/environment/_GeneralModuleFiles/atmosphereBase.h"
#include "architecture/utilities/bskLogging.h"
#include "architecture/messaging/messaging.h" // ReadFunctor / Message
#include "customAtmStatusMsgPayload.h"
#include "CustomAtmStatusMsgPayload.h"

/*! @brief exponential atmosphere model (plugin example) */
class CustomExponentialAtmosphere : public AtmosphereBase
Expand Down
16 changes: 15 additions & 1 deletion examples/custom-atm-plugin/swig/customExponentialAtmosphere.i
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,21 @@ from Basilisk.architecture.swig_common_model import *
%include "std_vector.i"
%include "std_string.i"

%include "sys_model.i"
// IMPORTANT: use %import (not %include) for Basilisk base-class modules.
//
// %import tells SWIG "these types live in an existing Python module — do not
// re-wrap them here." The generated Python class for this plugin will then
// inherit from Basilisk's cSysModel.SysModel, which is what Basilisk's
// simulation task manager expects when you call AddModelToTask().
//
// Using %include instead creates a *separate* SysModel type inside this
// module that is invisible to Basilisk's type system, causing a confusing
// runtime TypeError even though the C++ inheritance is correct.
%import "sys_model.i"

// Intermediate base classes that are NOT exposed by any Basilisk Python
// module can still be %included — SWIG wraps them locally and they inherit
// from the imported SysModel above, keeping the full chain intact.
%include "simulation/environment/_GeneralModuleFiles/atmosphereBase.h"
%include "customExponentialAtmosphere.h"

Expand Down
Loading