Skip to content

Commit 987c5ee

Browse files
authored
Merge pull request #417 from alphaville/feature/36-ga-win
GA CI: tests on Windows and more
2 parents 627c2df + 1ad5cd1 commit 987c5ee

File tree

10 files changed

+210
-32
lines changed

10 files changed

+210
-32
lines changed

.github/workflows/ci.yml

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ jobs:
2525
os: ubuntu-latest
2626
- name: macOS
2727
os: macos-latest
28+
- name: Windows
29+
os: windows-latest
2830
steps:
2931
- uses: actions/checkout@v5
3032

@@ -37,6 +39,7 @@ jobs:
3739
run: cargo test
3840

3941
- name: Cargo tests (RP and JEM)
42+
if: runner.os != 'Windows'
4043
run: |
4144
cargo test --features rp
4245
cargo test --features jem
@@ -57,6 +60,9 @@ jobs:
5760
- name: macOS
5861
os: macos-latest
5962
skip_rpi_test: 1
63+
- name: Windows
64+
os: windows-latest
65+
skip_rpi_test: 1
6066
env:
6167
DO_DOCKER: 0
6268
SKIP_RPI_TEST: ${{ matrix.skip_rpi_test }}
@@ -73,7 +79,7 @@ jobs:
7379
with:
7480
python-version: "3.12"
7581
cache: "pip"
76-
cache-dependency-path: open-codegen/setup.py
82+
cache-dependency-path: open-codegen/pyproject.toml
7783

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

112+
- name: Install Python package
113+
if: runner.os == 'Windows'
114+
working-directory: open-codegen
115+
run: |
116+
python -m pip install --upgrade pip
117+
python -m pip install .
118+
119+
- name: Run Python test.py
120+
if: runner.os == 'Windows'
121+
working-directory: open-codegen
122+
env:
123+
PYTHONPATH: .
124+
run: python -W ignore test/test.py -v
125+
106126
ros2_tests:
107127
name: ROS2 tests
108128
needs: python_tests
@@ -165,6 +185,8 @@ jobs:
165185
os: ubuntu-latest
166186
- name: macOS
167187
os: macos-latest
188+
- name: Windows
189+
os: windows-latest
168190
env:
169191
DO_DOCKER: 0
170192
steps:
@@ -179,7 +201,22 @@ jobs:
179201
with:
180202
python-version: "3.12"
181203
cache: "pip"
182-
cache-dependency-path: open-codegen/setup.py
204+
cache-dependency-path: open-codegen/pyproject.toml
183205

184206
- name: Run OCP Python tests
207+
if: runner.os != 'Windows'
185208
run: bash ./ci/script.sh ocp-tests
209+
210+
- name: Install Python package
211+
if: runner.os == 'Windows'
212+
working-directory: open-codegen
213+
run: |
214+
python -m pip install --upgrade pip
215+
python -m pip install .
216+
217+
- name: Run OCP Python tests
218+
if: runner.os == 'Windows'
219+
working-directory: open-codegen
220+
env:
221+
PYTHONPATH: .
222+
run: python -W ignore test/test_ocp.py -v

open-codegen/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Note: This is the Changelog file of `opengen` - the Python interface of OpEn
2323
- Added helpful `__repr__` methods to generated Python binding response/status/error objects, TCP solver response/error objects, and `GeneratedOptimizer` for easier inspection and debugging
2424
- 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.
2525
- 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
26+
- 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.
2627

2728

2829
## [0.10.1] - 2026-03-25

open-codegen/opengen/builder/ros_builder.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import logging
77
import os
88
import shutil
9+
import sys
910

1011
import jinja2
1112

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

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

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

169-
lib_file_name = 'lib' + self._meta.optimizer_name + '.a'
170+
if sys.platform == "win32":
171+
lib_file_name = self._meta.optimizer_name + '.lib'
172+
else:
173+
lib_file_name = 'lib' + self._meta.optimizer_name + '.a'
170174
target_lib_file_name = os.path.abspath(
171175
os.path.join(target_ros_dir, 'extern_lib', lib_file_name))
172176
original_lib_file = os.path.abspath(
@@ -194,7 +198,7 @@ def _generate_ros_params_file(self):
194198
template = self._template('open_params.yaml')
195199
output_template = template.render(meta=self._meta, ros=self._ros_config)
196200
target_yaml_fname = os.path.join(target_ros_dir, "config", "open_params.yaml")
197-
with open(target_yaml_fname, "w") as fh:
201+
with open(target_yaml_fname, "w", encoding="utf-8") as fh:
198202
fh.write(output_template)
199203

200204
def _generate_ros_node_header(self):
@@ -208,7 +212,7 @@ def _generate_ros_node_header(self):
208212
solver_config=self._solver_config)
209213
target_rosnode_header_path = os.path.join(
210214
target_ros_dir, "include", "open_optimizer.hpp")
211-
with open(target_rosnode_header_path, "w") as fh:
215+
with open(target_rosnode_header_path, "w", encoding="utf-8") as fh:
212216
fh.write(output_template)
213217

214218
def _generate_ros_node_cpp(self):
@@ -221,7 +225,7 @@ def _generate_ros_node_cpp(self):
221225
ros=self._ros_config,
222226
timestamp_created=datetime.datetime.now())
223227
target_rosnode_cpp_path = os.path.join(target_ros_dir, "src", "open_optimizer.cpp")
224-
with open(target_rosnode_cpp_path, "w") as fh:
228+
with open(target_rosnode_cpp_path, "w", encoding="utf-8") as fh:
225229
fh.write(output_template)
226230

227231
def _generate_ros_launch_file(self):
@@ -232,7 +236,7 @@ def _generate_ros_launch_file(self):
232236
output_template = template.render(meta=self._meta, ros=self._ros_config)
233237
target_rosnode_launch_path = os.path.join(
234238
target_ros_dir, "launch", self._launch_file_name)
235-
with open(target_rosnode_launch_path, "w") as fh:
239+
with open(target_rosnode_launch_path, "w", encoding="utf-8") as fh:
236240
fh.write(output_template)
237241

238242
def _generate_ros_readme_file(self):
@@ -242,7 +246,7 @@ def _generate_ros_readme_file(self):
242246
template = self._template('README.md')
243247
output_template = template.render(ros=self._ros_config)
244248
target_readme_path = os.path.join(target_ros_dir, "README.md")
245-
with open(target_readme_path, "w") as fh:
249+
with open(target_readme_path, "w", encoding="utf-8") as fh:
246250
fh.write(output_template)
247251

248252
def _symbolic_link_info_message(self):

open-codegen/opengen/config/build_config.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ def open_version(self):
105105
@property
106106
def local_path(self):
107107
"""Local path of OpEn (if any)"""
108-
return self.__local_path
108+
if self.__local_path is None:
109+
return None
110+
# Cargo.toml accepts forward slashes on Windows, while raw backslashes
111+
# inside TOML strings are treated as escape sequences.
112+
return self.__local_path.replace("\\", "/")
109113

110114
@property
111115
def build_c_bindings(self):
@@ -231,7 +235,7 @@ def with_open_version(self, open_version="*", local_path=None):
231235
:return: current instance of BuildConfiguration
232236
"""
233237
self.__open_version = open_version
234-
self.__local_path = local_path
238+
self.__local_path = None if local_path is None else str(local_path)
235239
return self
236240

237241
def with_build_c_bindings(self, build_c_bindings=True):

open-codegen/opengen/tcp/optimizer_tcp_manager.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,16 @@ def __load_tcp_details(self):
9191
with open(yaml_file, 'r') as stream:
9292
self.__optimizer_details = yaml.safe_load(stream)
9393

94+
@staticmethod
95+
def __client_ip_for_connection(ip):
96+
# `0.0.0.0` is a valid bind address for the server, but it is not a
97+
# routable destination for a client connection on Windows.
98+
return '127.0.0.1' if ip == '0.0.0.0' else ip
99+
94100
@retry(tries=10, delay=1)
95101
def __obtain_socket_connection(self):
96102
tcp_data = self.__optimizer_details
97-
ip = tcp_data['tcp']['ip']
103+
ip = self.__client_ip_for_connection(tcp_data['tcp']['ip'])
98104
port = tcp_data['tcp']['port']
99105
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
100106
try:
@@ -132,7 +138,7 @@ def ping(self):
132138

133139
def __check_if_server_is_running(self):
134140
tcp_data = self.__optimizer_details
135-
ip = tcp_data['tcp']['ip']
141+
ip = self.__client_ip_for_connection(tcp_data['tcp']['ip'])
136142
port = tcp_data['tcp']['port']
137143
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as s:
138144
result = 0 == s.connect_ex((ip, port))

open-codegen/opengen/templates/c/example_cmakelists.txt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cmake_minimum_required(VERSION 3.5)
1+
cmake_minimum_required(VERSION 3.10)
22

33
# Project name
44
project({{meta.optimizer_name}})
@@ -35,6 +35,21 @@ if(CMAKE_DL_LIBS)
3535
target_link_libraries(optimizer PRIVATE ${CMAKE_DL_LIBS})
3636
endif()
3737

38+
if(WIN32)
39+
# Rust static libraries built with the MSVC toolchain depend on a small set
40+
# of Windows system libraries that must be linked by the final C executable.
41+
target_link_libraries(
42+
optimizer
43+
PRIVATE
44+
advapi32
45+
bcrypt
46+
kernel32
47+
ntdll
48+
userenv
49+
ws2_32
50+
)
51+
endif()
52+
3853
add_custom_target(run
3954
COMMAND $<TARGET_FILE:optimizer>
4055
DEPENDS optimizer

open-codegen/opengen/templates/ros/CMakeLists.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,24 @@ include_directories(
2929

3030
set(NODE_NAME {{ros.node_name}})
3131
add_executable(${NODE_NAME} src/open_optimizer.cpp)
32+
if(WIN32)
33+
set(OPEN_STATIC_LIB ${PROJECT_SOURCE_DIR}/extern_lib/{{meta.optimizer_name}}.lib)
34+
else()
35+
set(OPEN_STATIC_LIB ${PROJECT_SOURCE_DIR}/extern_lib/lib{{meta.optimizer_name}}.a)
36+
endif()
3237
target_link_libraries(
3338
${NODE_NAME}
34-
${PROJECT_SOURCE_DIR}/extern_lib/lib{{meta.optimizer_name}}.a)
39+
${OPEN_STATIC_LIB})
40+
if(WIN32)
41+
target_link_libraries(
42+
${NODE_NAME}
43+
${catkin_LIBRARIES})
44+
else()
3545
target_link_libraries(
3646
${NODE_NAME}
3747
m dl
3848
${catkin_LIBRARIES})
49+
endif()
3950
add_dependencies(
4051
${NODE_NAME}
4152
${${PROJECT_NAME}_EXPORTED_TARGETS}

open-codegen/opengen/templates/ros2/CMakeLists.txt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,22 @@ include_directories(
4242
set(NODE_NAME {{ros.node_name}})
4343
add_executable(${NODE_NAME} src/open_optimizer.cpp)
4444
ament_target_dependencies(${NODE_NAME} rclcpp)
45+
if(WIN32)
46+
set(OPEN_STATIC_LIB ${PROJECT_SOURCE_DIR}/extern_lib/{{meta.optimizer_name}}.lib)
47+
else()
48+
set(OPEN_STATIC_LIB ${PROJECT_SOURCE_DIR}/extern_lib/lib{{meta.optimizer_name}}.a)
49+
endif()
4550
target_link_libraries(
4651
${NODE_NAME}
47-
${PROJECT_SOURCE_DIR}/extern_lib/lib{{meta.optimizer_name}}.a
48-
m
49-
dl
52+
${OPEN_STATIC_LIB}
5053
)
54+
if(NOT WIN32)
55+
target_link_libraries(
56+
${NODE_NAME}
57+
m
58+
dl
59+
)
60+
endif()
5161
rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} "rosidl_typesupport_cpp")
5262
target_link_libraries(${NODE_NAME} "${cpp_typesupport_target}")
5363

0 commit comments

Comments
 (0)