Skip to content

Commit 31d57c7

Browse files
authored
Merge pull request #368 from alphaville/fix/366-cross-compilation
Fix cross compilation issue
2 parents 9bf1c6e + ef9656d commit 31d57c7

File tree

13 files changed

+262
-33
lines changed

13 files changed

+262
-33
lines changed

.github/workflows/ci.yml

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ${{ matrix.os }}
1111
strategy:
1212
matrix:
13-
os: [ubuntu-20.04, ubuntu-latest]
13+
os: [ubuntu-latest]
1414
env:
1515
DO_DOCKER: 0
1616
steps:
@@ -28,9 +28,25 @@ jobs:
2828
with:
2929
python-version: '3.12'
3030
architecture: 'x64'
31-
- run: cargo test --features rp
32-
- run: cargo test --features jem
33-
- run: bash ./ci/script.sh
31+
- name: Install ARM cross-compiler and C libraries
32+
run: |
33+
sudo apt-get update
34+
sudo apt-get install -y gcc-arm-linux-gnueabihf libc6-dev-armhf-cross
35+
# If icasadi_rosenbrock or other deps need C++:
36+
# sudo apt-get install -y g++-arm-linux-gnueabihf
37+
- name: Cargo tests (RP and JEM)
38+
run: |
39+
cargo test --features rp
40+
cargo test --features jem
41+
- name: Run tests (script.sh)
42+
# Set environment variables for the cc crate
43+
env:
44+
CC_arm_unknown_linux_gnueabihf: arm-linux-gnueabihf-gcc
45+
AR_arm_unknown_linux_gnueabihf: arm-linux-gnueabihf-ar
46+
# If C++ is involved and you installed g++-arm-linux-gnueabihf:
47+
# CXX_arm_unknown_linux_gnueabihf: arm-linux-gnueabihf-g++
48+
run: |
49+
bash ./ci/script.sh
3450
3551
ci_macos:
3652
runs-on: ${{ matrix.os }}
@@ -55,4 +71,30 @@ jobs:
5571
python-version: '3.12'
5672
- run: cargo test --features rp
5773
- run: cargo test --features jem
58-
- run: bash ./ci/script.sh
74+
- name: Install ARM cross-compiler toolchain (via Homebrew)
75+
run: |
76+
# Tap the repository that provides the cross-compiler
77+
brew tap messense/macos-cross-toolchains
78+
# Update brew to ensure the tap is recognized (can sometimes be needed)
79+
brew update
80+
# Install the full toolchain (includes gcc, binutils, sysroot)
81+
# This specific formula provides the entire toolchain.
82+
brew install arm-unknown-linux-gnueabihf
83+
84+
# The above `brew install` might have linking conflicts if other partial
85+
# toolchains were somehow pre-installed or installed by other steps.
86+
# If it fails with link errors, you might need:
87+
# brew link --overwrite arm-unknown-linux-gnueabihf
88+
89+
# Verify the compiler is found
90+
which arm-linux-gnueabihf-gcc || (echo "arm-linux-gnueabihf-gcc not found in PATH" && exit 1)
91+
- name: Run tests (script.sh)
92+
# Set environment variables for the cc crate and PyO3 (if needed)
93+
env:
94+
CC_arm_unknown_linux_gnueabihf: arm-linux-gnueabihf-gcc
95+
AR_arm_unknown_linux_gnueabihf: arm-linux-gnueabihf-ar
96+
# If you are building PyO3 bindings and need to specify Python libs from the sysroot:
97+
# PYO3_CROSS_LIB_DIR: "/opt/homebrew/opt/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/sysroot/usr/lib" # Adjust path and Python version
98+
# PYO3_CROSS_INCLUDE_DIR: "/opt/homebrew/opt/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/sysroot/usr/include/python3.x" # Adjust path and Python version
99+
run: |
100+
bash ./ci/script.sh

.github/workflows/dox.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88

99
jobs:
1010
deploy:
11-
runs-on: ubuntu-20.04
11+
runs-on: ubuntu-latest
1212

1313
steps:
1414
- uses: actions/checkout@v2

ci/script.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,15 @@ regular_test() {
4444
# --- install opengen
4545
pip install .
4646

47+
# --- rust dependencies
48+
rustup update
49+
rustup target add arm-unknown-linux-gnueabihf
50+
4751
# --- run the tests
4852
export PYTHONPATH=.
4953
python -W ignore test/test_constraints.py -v
5054
python -W ignore test/test.py -v
55+
python -W ignore test/test_raspberry_pi.py -v
5156

5257

5358
# Run Clippy for generated optimizers

docs/python-advanced.md

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ A complete list of solver options is given in the following table
8181

8282
## Build options
8383

84+
### Build mode
85+
8486
During the design phase, one needs to experiment with the problem
8587
formulation and solver parameters. This is way the default build
8688
mode is the "debug" mode, which compiles fast, but it suboptimal.
@@ -99,8 +101,10 @@ build_config.with_build_mode(
99101
og.config.BuildConfiguration.RELEASE_MODE)
100102
```
101103

104+
### Cross-compilation
105+
102106
You can either compile for your own system, or cross-compile for a
103-
different target system. For example, to cross-compile for a Raspberry Pi,
107+
different target system. For example, to cross-compile for a **Raspberry Pi**,
104108
set the following option
105109

106110
```python
@@ -113,8 +117,83 @@ or
113117
build_config.with_target_system("rpi") # Raspberry Pi
114118
```
115119

116-
Note that you need to install the necessary target first.
120+
Note that you need to install the necessary target first.
121+
122+
<details>
123+
<summary><b>See setup details</b></summary>
124+
To cross-compile for a Raspberry Pi you need to run the following in your terminal
125+
126+
```bash
127+
rustup target add arm-unknown-linux-gnueabihf
128+
```
129+
130+
You also need to install the following dependencies
131+
132+
<!--DOCUSAURUS_CODE_TABS-->
133+
134+
<!--Linux-->
135+
```bash
136+
sudo apt-get update
137+
sudo apt-get install -y gcc-arm-linux-gnueabihf libc6-dev-armhf-cross
138+
```
139+
140+
<!--MacOS-->
141+
```bash
142+
# Tap the repository that provides the cross-compiler
143+
brew tap messense/macos-cross-toolchains
144+
# Update brew to ensure the tap is recognized (can sometimes be needed)
145+
brew update
146+
# Install the full toolchain (includes gcc, binutils, sysroot)
147+
# This specific formula provides the entire toolchain.
148+
brew install arm-unknown-linux-gnueabihf
149+
150+
# Verify the compiler is found
151+
which arm-linux-gnueabihf-gcc || (echo "arm-linux-gnueabihf-gcc not found in PATH" && exit 1)
152+
```
153+
<!--END_DOCUSAURUS_CODE_TABS-->
154+
</details>
155+
156+
<br>
157+
If you need to compile for a target other than `arm-linux-gnueabihf-gcc` (`rpi`)
158+
some manual configuration may be needed (you may need to install the target
159+
and/or a compiler/linker) and you may need to edit the auto-generated
160+
`.cargo/config.toml` files you will find in your auto-generated solvers.
161+
162+
<details>
163+
<summary><b>Non-supported targets</b></summary>
164+
The auto-generated `.cargo/config.toml` files contain entries like
165+
166+
```toml
167+
[target.arm-unknown-linux-gnueabihf]
168+
linker="arm-linux-gnueabihf-gcc"
169+
```
170+
171+
Here you may have to insert manually your own target.
172+
Feel free to open an [issue](https://github.com/alphaville/optimization-engine/issues)
173+
on GitHub if you would like us to add support for a particular target (create a feature
174+
request); see the [contributing guidelines](https://alphaville.github.io/optimization-engine/docs/contributing).
175+
</details>
176+
177+
178+
When cross-compiling for a Raspberry Pi you may want to configure a TCP server
179+
so you can call the optimizer remotely. You can find more information about this
180+
[below](#tcpip-interface).
181+
Once you have cross-compiled, locate the file
182+
```text
183+
{your_optimizer}/tcp_iface_{your_optimizer}/target/arm-unknown-linux-gnueabihf/release/tcp_iface_{your_optimizer}
184+
```
185+
—where `{your_optimizer}` is the name of your optimizer—and copy it to your Raspberry Pi.
186+
On your Raspberry, change the permissions so you can execute this file
187+
```bash
188+
chmod u+x ./tcp_iface_{your_optimizer}
189+
```
190+
and [run it](https://alphaville.github.io/optimization-engine/docs/python-tcp-ip). Your OpEn server is live.
191+
Read also the [documentation](https://alphaville.github.io/optimization-engine/docs/python-tcp-ip)
192+
on the TCP sockets protocol of OpEn servers.
193+
194+
### Other build options
117195

196+
All build options are shown below
118197

119198
| Method | Explanation |
120199
|-------------------------------|---------------------------------------------|

open-codegen/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
Note: This is the Changelog file of `opengen` - the Python interface of OpEn
99

10+
## [0.9.4] - 2025-05-08
11+
12+
13+
### Fixed
14+
15+
- Fixed issues with cross compilation (each sub-project has its own `.cargo/config.toml`) and updated [documentation](https://alphaville.github.io/optimization-engine/docs/python-advanced#cross-compilation)
16+
17+
### Changed
18+
19+
- Rename auto-generated bindings file from `.cargo/config` to `.cargo/config.toml` (backwards compatible change)
20+
- Updated min cmake version from 2.8 to 3.5
21+
- Updated auto-generated example C/C++ bindings
22+
1023

1124
## [0.9.3] - 2024-12-06
1225

@@ -226,6 +239,7 @@ Note: This is the Changelog file of `opengen` - the Python interface of OpEn
226239
* Fixed `lbfgs` typo
227240

228241

242+
[0.9.4]: https://github.com/alphaville/optimization-engine/compare/opengen-0.9.3...opengen-0.9.4
229243
[0.9.3]: https://github.com/alphaville/optimization-engine/compare/opengen-0.9.2...opengen-0.9.3
230244
[0.9.2]: https://github.com/alphaville/optimization-engine/compare/opengen-0.9.1...opengen-0.9.2
231245
[0.9.1]: https://github.com/alphaville/optimization-engine/compare/opengen-0.9.0...opengen-0.9.1

open-codegen/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.9.3
1+
0.9.4

open-codegen/opengen/builder/optimizer_builder.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,6 @@ def __prepare_target_project(self):
160160
"""Creates folder structure
161161
162162
Creates necessary folders
163-
Runs `cargo init` in that folder
164-
165163
"""
166164
self.__logger.info("Creating necessary folders")
167165

@@ -173,6 +171,13 @@ def __prepare_target_project(self):
173171
os.makedirs(target_dir)
174172
else:
175173
make_dir_if_not_exists(target_dir)
174+
175+
# make folder {root}/.cargo
176+
dot_cargo_dir = os.path.join(target_dir, ".cargo")
177+
make_dir_if_not_exists(dot_cargo_dir)
178+
# copy cargo_config.toml into .cargo/config.toml
179+
cargo_config_file = os.path.join(og_dfn.templates_dir(), 'cargo_config.toml')
180+
shutil.copy(cargo_config_file, os.path.join(dot_cargo_dir, 'config.toml'))
176181

177182
def __copy_icasadi_to_target(self):
178183
"""
@@ -181,13 +186,21 @@ def __copy_icasadi_to_target(self):
181186
self.__logger.info("Copying icasadi interface to target directory")
182187
origin_icasadi_dir = og_dfn.original_icasadi_dir()
183188
target_icasadi_dir = self.__icasadi_target_dir()
184-
if not os.path.exists(target_icasadi_dir):
185-
os.makedirs(target_icasadi_dir)
186-
shutil.rmtree(target_icasadi_dir)
189+
target_icasadi_cargo_config_dir = os.path.join(target_icasadi_dir, ".cargo")
190+
191+
if os.path.exists(target_icasadi_dir):
192+
shutil.rmtree(target_icasadi_dir)
193+
187194
shutil.copytree(origin_icasadi_dir,
188195
target_icasadi_dir,
189196
ignore=shutil.ignore_patterns(
190197
'*.lock', 'ci*', 'target', 'auto*'))
198+
199+
# Copy cargo_config.toml into .cargo/config.toml
200+
make_dir_if_not_exists(target_icasadi_cargo_config_dir)
201+
cargo_config_file = os.path.join(og_dfn.templates_dir(), 'cargo_config.toml')
202+
shutil.copy(cargo_config_file, os.path.join(
203+
target_icasadi_cargo_config_dir, 'config.toml'))
191204

192205
def __generate_icasadi_cargo_toml(self):
193206
"""
@@ -571,7 +584,7 @@ def __generate_build_rs(self):
571584
def __build_optimizer(self):
572585
target_dir = os.path.abspath(self.__target_dir())
573586
command = self.__make_build_command()
574-
p = subprocess.Popen(command, cwd=target_dir)
587+
p = subprocess.Popen(command, cwd=target_dir, shell=False)
575588
process_completion = p.wait()
576589
if process_completion != 0:
577590
raise Exception('Rust build failed')
@@ -676,13 +689,13 @@ def __generate_code_python_bindings(self):
676689
with open(target_python_rs_path, "w") as fh:
677690
fh.write(python_rs_output_template)
678691

679-
# move cargo_config into .cargo/config
692+
# copy cargo_config into .cargo/config
680693
target_cargo_config_dir = os.path.join(python_bindings_dir, '.cargo')
681694
make_dir_if_not_exists(target_cargo_config_dir)
682695
cargo_config_file = os.path.join(
683-
og_dfn.templates_dir(), 'python', 'cargo_config')
696+
og_dfn.templates_dir(), 'cargo_config.toml')
684697
shutil.copy(cargo_config_file, os.path.join(
685-
target_cargo_config_dir, 'config'))
698+
target_cargo_config_dir, 'config.toml'))
686699

687700
def __generate_code_tcp_interface(self):
688701
self.__logger.info(
@@ -694,10 +707,12 @@ def __generate_code_tcp_interface(self):
694707
tcp_iface_dir_name = _TCP_IFACE_PREFIX + self.__meta.optimizer_name
695708
tcp_iface_dir = os.path.join(target_dir, tcp_iface_dir_name)
696709
tcp_iface_source_dir = os.path.join(tcp_iface_dir, "src")
710+
tcp_iface_cargo_config_dir = os.path.join(tcp_iface_dir, ".cargo")
697711

698712
# make tcp_iface/ and tcp_iface/src
699713
make_dir_if_not_exists(tcp_iface_dir)
700714
make_dir_if_not_exists(tcp_iface_source_dir)
715+
make_dir_if_not_exists(tcp_iface_cargo_config_dir)
701716

702717
# generate tcp_server.rs for tcp_iface
703718
tcp_rs_template = OpEnOptimizerBuilder.__get_template(
@@ -718,6 +733,12 @@ def __generate_code_tcp_interface(self):
718733
target_tcp_rs_path = os.path.join(tcp_iface_dir, "Cargo.toml")
719734
with open(target_tcp_rs_path, "w") as fh:
720735
fh.write(tcp_rs_output_template)
736+
737+
# Copy cargo_config.toml into .cargo/config.toml
738+
cargo_config_file = os.path.join(
739+
og_dfn.templates_dir(), 'cargo_config.toml')
740+
shutil.copy(cargo_config_file, os.path.join(
741+
tcp_iface_cargo_config_dir, 'config.toml'))
721742

722743
def __generate_yaml_data_file(self):
723744
self.__logger.info("Generating YAML configuration file")
@@ -850,7 +871,7 @@ def build(self):
850871
"""
851872
self.__initialize() # initialize default value (if not provided)
852873
self.__check_user_provided_parameters() # check the provided parameters
853-
self.__prepare_target_project() # create folders; init cargo project
874+
self.__prepare_target_project() # create folders
854875
self.__copy_icasadi_to_target() # copy icasadi/ files to target dir
855876
self.__generate_icasadi_cargo_toml() # generate icasadi's Cargo.toml file
856877
self.__generate_cargo_toml() # generate Cargo.toml using template

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

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

33
# Project name
44
project({{meta.optimizer_name}})

open-codegen/opengen/templates/c/example_optimizer_c_bindings.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,20 @@
33
*
44
* Compile with:
55
*
6-
* gcc -Wall -std=c99 -pedantic \
6+
* $ gcc -Wall -std=c99 -pedantic \
77
example_optimizer.c -l:lib{{meta.optimizer_name}}.a \
88
-L./target/{{build_config.build_mode}} -pthread -lm -ldl \
99
-o optimizer
1010
*
11+
* OR ...
12+
*
13+
* $ gcc -Wall -std=c99 -pedantic \
14+
example_optimizer.c -l{{meta.optimizer_name}} \
15+
-L./target/{{build_config.build_mode}} -pthread -lm -ldl \
16+
-o optimizer
17+
*
18+
* Or simply do:
19+
* cmake .; make run
1120
*/
1221

1322
#include <stdio.h>
@@ -17,7 +26,7 @@
1726
* Feel free to customize the following code...
1827
*/
1928

20-
int main() {
29+
int main(void) {
2130
int i;
2231

2332
/* parameters */

0 commit comments

Comments
 (0)