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
41 changes: 39 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
os: ubuntu-latest
- name: macOS
os: macos-latest
- name: Windows
os: windows-latest
steps:
- uses: actions/checkout@v5

Expand All @@ -37,6 +39,7 @@ jobs:
run: cargo test

- name: Cargo tests (RP and JEM)
if: runner.os != 'Windows'
run: |
cargo test --features rp
cargo test --features jem
Expand All @@ -57,6 +60,9 @@ jobs:
- name: macOS
os: macos-latest
skip_rpi_test: 1
- name: Windows
os: windows-latest
skip_rpi_test: 1
env:
DO_DOCKER: 0
SKIP_RPI_TEST: ${{ matrix.skip_rpi_test }}
Expand All @@ -73,7 +79,7 @@ jobs:
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: open-codegen/setup.py
cache-dependency-path: open-codegen/pyproject.toml

- uses: egor-tensin/setup-clang@v1
if: runner.os == 'Linux'
Expand Down Expand Up @@ -103,6 +109,20 @@ jobs:
if: runner.os == 'macOS'
run: bash ./ci/script.sh python-tests

- name: Install Python package
if: runner.os == 'Windows'
working-directory: open-codegen
run: |
python -m pip install --upgrade pip
python -m pip install .

- name: Run Python test.py
if: runner.os == 'Windows'
working-directory: open-codegen
env:
PYTHONPATH: .
run: python -W ignore test/test.py -v

ros2_tests:
name: ROS2 tests
needs: python_tests
Expand Down Expand Up @@ -165,6 +185,8 @@ jobs:
os: ubuntu-latest
- name: macOS
os: macos-latest
- name: Windows
os: windows-latest
env:
DO_DOCKER: 0
steps:
Expand All @@ -179,7 +201,22 @@ jobs:
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: open-codegen/setup.py
cache-dependency-path: open-codegen/pyproject.toml

- name: Run OCP Python tests
if: runner.os != 'Windows'
run: bash ./ci/script.sh ocp-tests

- name: Install Python package
if: runner.os == 'Windows'
working-directory: open-codegen
run: |
python -m pip install --upgrade pip
python -m pip install .

- name: Run OCP Python tests
if: runner.os == 'Windows'
working-directory: open-codegen
env:
PYTHONPATH: .
run: python -W ignore test/test_ocp.py -v
1 change: 1 addition & 0 deletions open-codegen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Note: This is the Changelog file of `opengen` - the Python interface of OpEn
- Added helpful `__repr__` methods to generated Python binding response/status/error objects, TCP solver response/error objects, and `GeneratedOptimizer` for easier inspection and debugging
- Updated generated TCP server and C interface templates to work with the richer Rust solver error model and expose better failure information to clients. Updated auto-generated `CMakeLists.txt` file. Tighter unit tests.
- ROS2 generated packages now publish detailed `error_code` and `error_message` fields, plus `STATUS_INVALID_REQUEST`, so invalid requests and solver failures are reported explicitly instead of being silently ignored
- Extended GitHub Actions CI to run Python, OCP, and generated-code tests on Windows, and fixed multiple Windows-specific code generation, path, encoding, TCP, and C/CMake compatibility issues.


## [0.10.1] - 2026-03-25
Expand Down
20 changes: 12 additions & 8 deletions open-codegen/opengen/builder/ros_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import os
import shutil
import sys

import jinja2

Expand Down Expand Up @@ -141,7 +142,7 @@ def _generate_ros_package_xml(self):
template = self._template('package.xml')
output_template = template.render(meta=self._meta, ros=self._ros_config)
target_rospkg_path = os.path.join(target_ros_dir, "package.xml")
with open(target_rospkg_path, "w") as fh:
with open(target_rospkg_path, "w", encoding="utf-8") as fh:
fh.write(output_template)

def _generate_ros_cmakelists(self):
Expand All @@ -151,7 +152,7 @@ def _generate_ros_cmakelists(self):
template = self._template('CMakeLists.txt')
output_template = template.render(meta=self._meta, ros=self._ros_config)
target_rospkg_path = os.path.join(target_ros_dir, "CMakeLists.txt")
with open(target_rospkg_path, "w") as fh:
with open(target_rospkg_path, "w", encoding="utf-8") as fh:
fh.write(output_template)

def _copy_ros_files(self):
Expand All @@ -166,7 +167,10 @@ def _copy_ros_files(self):
os.path.join(self._target_dir(), header_file_name))
shutil.copyfile(original_include_file, target_include_filename)

lib_file_name = 'lib' + self._meta.optimizer_name + '.a'
if sys.platform == "win32":
lib_file_name = self._meta.optimizer_name + '.lib'
else:
lib_file_name = 'lib' + self._meta.optimizer_name + '.a'
target_lib_file_name = os.path.abspath(
os.path.join(target_ros_dir, 'extern_lib', lib_file_name))
original_lib_file = os.path.abspath(
Expand Down Expand Up @@ -194,7 +198,7 @@ def _generate_ros_params_file(self):
template = self._template('open_params.yaml')
output_template = template.render(meta=self._meta, ros=self._ros_config)
target_yaml_fname = os.path.join(target_ros_dir, "config", "open_params.yaml")
with open(target_yaml_fname, "w") as fh:
with open(target_yaml_fname, "w", encoding="utf-8") as fh:
fh.write(output_template)

def _generate_ros_node_header(self):
Expand All @@ -208,7 +212,7 @@ def _generate_ros_node_header(self):
solver_config=self._solver_config)
target_rosnode_header_path = os.path.join(
target_ros_dir, "include", "open_optimizer.hpp")
with open(target_rosnode_header_path, "w") as fh:
with open(target_rosnode_header_path, "w", encoding="utf-8") as fh:
fh.write(output_template)

def _generate_ros_node_cpp(self):
Expand All @@ -221,7 +225,7 @@ def _generate_ros_node_cpp(self):
ros=self._ros_config,
timestamp_created=datetime.datetime.now())
target_rosnode_cpp_path = os.path.join(target_ros_dir, "src", "open_optimizer.cpp")
with open(target_rosnode_cpp_path, "w") as fh:
with open(target_rosnode_cpp_path, "w", encoding="utf-8") as fh:
fh.write(output_template)

def _generate_ros_launch_file(self):
Expand All @@ -232,7 +236,7 @@ def _generate_ros_launch_file(self):
output_template = template.render(meta=self._meta, ros=self._ros_config)
target_rosnode_launch_path = os.path.join(
target_ros_dir, "launch", self._launch_file_name)
with open(target_rosnode_launch_path, "w") as fh:
with open(target_rosnode_launch_path, "w", encoding="utf-8") as fh:
fh.write(output_template)

def _generate_ros_readme_file(self):
Expand All @@ -242,7 +246,7 @@ def _generate_ros_readme_file(self):
template = self._template('README.md')
output_template = template.render(ros=self._ros_config)
target_readme_path = os.path.join(target_ros_dir, "README.md")
with open(target_readme_path, "w") as fh:
with open(target_readme_path, "w", encoding="utf-8") as fh:
fh.write(output_template)

def _symbolic_link_info_message(self):
Expand Down
8 changes: 6 additions & 2 deletions open-codegen/opengen/config/build_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ def open_version(self):
@property
def local_path(self):
"""Local path of OpEn (if any)"""
return self.__local_path
if self.__local_path is None:
return None
# Cargo.toml accepts forward slashes on Windows, while raw backslashes
# inside TOML strings are treated as escape sequences.
return self.__local_path.replace("\\", "/")

@property
def build_c_bindings(self):
Expand Down Expand Up @@ -231,7 +235,7 @@ def with_open_version(self, open_version="*", local_path=None):
:return: current instance of BuildConfiguration
"""
self.__open_version = open_version
self.__local_path = local_path
self.__local_path = None if local_path is None else str(local_path)
return self

def with_build_c_bindings(self, build_c_bindings=True):
Expand Down
10 changes: 8 additions & 2 deletions open-codegen/opengen/tcp/optimizer_tcp_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,16 @@ def __load_tcp_details(self):
with open(yaml_file, 'r') as stream:
self.__optimizer_details = yaml.safe_load(stream)

@staticmethod
def __client_ip_for_connection(ip):
# `0.0.0.0` is a valid bind address for the server, but it is not a
# routable destination for a client connection on Windows.
return '127.0.0.1' if ip == '0.0.0.0' else ip

@retry(tries=10, delay=1)
def __obtain_socket_connection(self):
tcp_data = self.__optimizer_details
ip = tcp_data['tcp']['ip']
ip = self.__client_ip_for_connection(tcp_data['tcp']['ip'])
port = tcp_data['tcp']['port']
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
try:
Expand Down Expand Up @@ -132,7 +138,7 @@ def ping(self):

def __check_if_server_is_running(self):
tcp_data = self.__optimizer_details
ip = tcp_data['tcp']['ip']
ip = self.__client_ip_for_connection(tcp_data['tcp']['ip'])
port = tcp_data['tcp']['port']
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as s:
result = 0 == s.connect_ex((ip, port))
Expand Down
17 changes: 16 additions & 1 deletion open-codegen/opengen/templates/c/example_cmakelists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.10)

# Project name
project({{meta.optimizer_name}})
Expand Down Expand Up @@ -35,6 +35,21 @@ if(CMAKE_DL_LIBS)
target_link_libraries(optimizer PRIVATE ${CMAKE_DL_LIBS})
endif()

if(WIN32)
# Rust static libraries built with the MSVC toolchain depend on a small set
# of Windows system libraries that must be linked by the final C executable.
target_link_libraries(
optimizer
PRIVATE
advapi32
bcrypt
kernel32
ntdll
userenv
ws2_32
)
endif()

add_custom_target(run
COMMAND $<TARGET_FILE:optimizer>
DEPENDS optimizer
Expand Down
13 changes: 12 additions & 1 deletion open-codegen/opengen/templates/ros/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,24 @@ include_directories(

set(NODE_NAME {{ros.node_name}})
add_executable(${NODE_NAME} src/open_optimizer.cpp)
if(WIN32)
set(OPEN_STATIC_LIB ${PROJECT_SOURCE_DIR}/extern_lib/{{meta.optimizer_name}}.lib)
else()
set(OPEN_STATIC_LIB ${PROJECT_SOURCE_DIR}/extern_lib/lib{{meta.optimizer_name}}.a)
endif()
target_link_libraries(
${NODE_NAME}
${PROJECT_SOURCE_DIR}/extern_lib/lib{{meta.optimizer_name}}.a)
${OPEN_STATIC_LIB})
if(WIN32)
target_link_libraries(
${NODE_NAME}
${catkin_LIBRARIES})
else()
target_link_libraries(
${NODE_NAME}
m dl
${catkin_LIBRARIES})
endif()
add_dependencies(
${NODE_NAME}
${${PROJECT_NAME}_EXPORTED_TARGETS}
Expand Down
16 changes: 13 additions & 3 deletions open-codegen/opengen/templates/ros2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,22 @@ include_directories(
set(NODE_NAME {{ros.node_name}})
add_executable(${NODE_NAME} src/open_optimizer.cpp)
ament_target_dependencies(${NODE_NAME} rclcpp)
if(WIN32)
set(OPEN_STATIC_LIB ${PROJECT_SOURCE_DIR}/extern_lib/{{meta.optimizer_name}}.lib)
else()
set(OPEN_STATIC_LIB ${PROJECT_SOURCE_DIR}/extern_lib/lib{{meta.optimizer_name}}.a)
endif()
target_link_libraries(
${NODE_NAME}
${PROJECT_SOURCE_DIR}/extern_lib/lib{{meta.optimizer_name}}.a
m
dl
${OPEN_STATIC_LIB}
)
if(NOT WIN32)
target_link_libraries(
${NODE_NAME}
m
dl
)
endif()
rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} "rosidl_typesupport_cpp")
target_link_libraries(${NODE_NAME} "${cpp_typesupport_target}")

Expand Down
Loading