Skip to content

Commit

Permalink
Merge pull request #60 from pariterre/main
Browse files Browse the repository at this point in the history
Compilation on Windows
  • Loading branch information
Tim-Salzmann authored Jan 29, 2025
2 parents c4d7b2e + 7f3a4f0 commit f3681e8
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 24 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
strategy:
fail-fast: false
matrix:
runs-on: [ubuntu-latest, ubuntu-20.04, macos-latest]
runs-on: [ubuntu-latest, ubuntu-20.04, macos-latest, windows-latest]

name: Tests on ${{ matrix.runs-on }}
steps:
Expand Down Expand Up @@ -87,4 +87,4 @@ jobs:
pip install -r requirements_build.txt
pip install . -v --no-build-isolation
# pip install pytest
# pytest .
# pytest .
4 changes: 2 additions & 2 deletions l4casadi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from importlib_resources import files # type: ignore[no-redef]
import ctypes

from .l4casadi import L4CasADi, dynamic_lib_file_ending
from .l4casadi import L4CasADi, dynamic_lib_file_ending, dynamic_lib_file_starting

from . import naive
from . import realtime


file_dir = files('l4casadi')
lib_path = file_dir / 'lib' / ('libl4casadi' + dynamic_lib_file_ending())
lib_path = file_dir / 'lib' / (dynamic_lib_file_starting() + 'l4casadi' + dynamic_lib_file_ending())
ctypes.CDLL(str(lib_path), mode=ctypes.RTLD_GLOBAL)
87 changes: 70 additions & 17 deletions l4casadi/l4casadi.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@
from l4casadi.naive import NaiveL4CasADiModule


def dynamic_lib_file_starting():
system = platform.system()
if system == 'Darwin':
return 'lib'
elif system == 'Linux':
return 'lib'
elif system == 'Windows':
return ''


def dynamic_lib_file_ending():
system = platform.system()
if system == 'Darwin':
Expand Down Expand Up @@ -69,9 +79,6 @@ def __init__(self,
:param scripting: If True, the model is traced using TorchScript. If False, the model is compiled.
:param mutable: If True, enables updating the model online via the update method.
"""
if platform.system() == "Windows":
warnings.warn("L4CasADi is currently not supported for Windows.")

if not scripting:
warnings.warn("L4CasADi with Torch AOT compilation is experimental at this point and might not work as "
"expected.")
Expand Down Expand Up @@ -108,6 +115,8 @@ def __init__(self,

self._scripting = scripting

if mutable and platform.system() == "Windows":
raise RuntimeError('Online model update (mutable=True) is not supported on Windows.')
self._mutable = mutable

self._input_shape: Tuple[int, int] = (-1, -1)
Expand Down Expand Up @@ -230,7 +239,7 @@ def _load_built_library_as_external_cs_fun(self):
raise RuntimeError('L4CasADi model has not been built yet. Call `build` first.')
self._cs_fun = cs.external(
f'{self.name}',
f"{self.build_dir / f'lib{self.name}'}{dynamic_lib_file_ending()}"
f"{self.build_dir / f'{dynamic_lib_file_starting()}{self.name}'}{dynamic_lib_file_ending()}"
)

@staticmethod
Expand Down Expand Up @@ -319,20 +328,64 @@ def compile(self):
include_dir = files('l4casadi') / 'include'
lib_dir = file_dir / 'lib'

# call gcc
soname = 'install_name' if platform.system() == 'Darwin' else 'soname'
cxx11_abi = 1 if torch._C._GLIBCXX_USE_CXX11_ABI else 0
link_libl4casadi = " -ll4casadi" if not self.naive else ""
os_cmd = ("gcc"
" -fPIC -shared"
f" {self.build_dir / self.name}.cpp"
f" -o {self.build_dir / f'lib{self.name}'}{dynamic_lib_file_ending()}"
f" -I{include_dir} -L{lib_dir}"
f" -Wl,-{soname},lib{self.name}{dynamic_lib_file_ending()}"
f"{link_libl4casadi}"
" -lstdc++ -std=c++17"
f" -D_GLIBCXX_USE_CXX11_ABI={cxx11_abi}")
# If cmake is available on the system, use it to compile the dynamic library
if platform.system() != "Windows" and shutil.which("gcc"):
# If cmake is not installed, fall back to manual compilation using gcc (previous implementation)
soname = 'install_name' if platform.system() == 'Darwin' else 'soname'
cxx11_abi = 1 if torch._C._GLIBCXX_USE_CXX11_ABI else 0
link_libl4casadi = " -ll4casadi" if not self.naive else ""
os_cmd = ("gcc"
" -fPIC -shared"
f" {self.build_dir / self.name}.cpp"
f" -o {self.build_dir / f'lib{self.name}'}{dynamic_lib_file_ending()}"
f" -I{include_dir} -L{lib_dir}"
f" -Wl,-{soname},{dynamic_lib_file_starting()}{self.name}{dynamic_lib_file_ending()}"
f"{link_libl4casadi}"
" -lstdc++ -std=c++17"
f" -D_GLIBCXX_USE_CXX11_ABI={cxx11_abi}")

elif shutil.which("cmake"):
# get current working dir as posix
cwd = pathlib.Path('.').absolute()

linked_lib = f"target_link_libraries({self.name} l4casadi)" if not self.naive else ""
glibcxx_use_cxx11_abi = (
""
if platform.system == "Windows" else
f"add_definitions(-D_GLIBCXX_USE_CXX11_ABI={1 if torch._C._GLIBCXX_USE_CXX11_ABI else 0})"
)

cmake_content = f"""
cmake_minimum_required(VERSION 3.15)
project({self.name})
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
include_directories({include_dir.as_posix()})
link_directories({lib_dir.as_posix()})
add_library({self.name} SHARED {self.name}.cpp)
{linked_lib}
set_target_properties({self.name} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY {(cwd / self.build_dir).as_posix()}
RUNTIME_OUTPUT_DIRECTORY {(cwd / self.build_dir).as_posix()}
)
{glibcxx_use_cxx11_abi}
install(TARGETS {self.name} DESTINATION {(cwd / self.build_dir).as_posix()})
"""
with open(self.build_dir / "CMakeLists.txt", "w") as f:
f.write(cmake_content)

os_cmd = (
f"cmake -S {self.build_dir} -B {self.build_dir} -DCMAKE_RULE_MESSAGES=OFF && "
f"cmake --build {self.build_dir} --config=Release"
)
os_cmd += f"&& cmake --install {self.build_dir} --config=Release" if platform.system() == "Windows" else ""
else:
raise RuntimeError("Please install gcc (Linux and Mac) or cmake (Windows, Linux and Mac) to compile the dynamic library.")

status = os.system(os_cmd)
if status != 0:
raise Exception(f'Compilation failed!\n\nAttempted to execute OS command:\n{os_cmd}\n\n')
Expand Down
2 changes: 1 addition & 1 deletion libl4casadi/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(L4CasADi)

set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
set(CMAKE_COMPILE_WARNING_AS_ERROR OFF)

if (WIN32)
set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@


def compile_hook(manifest):
lib = manifest[0]
file_path = pathlib.Path(__file__).parent.resolve()
(file_path / 'l4casadi' / 'lib').mkdir(exist_ok=True)
(file_path / 'l4casadi' / 'include').mkdir(exist_ok=True)

# Copy lib
shutil.copy(lib, file_path / 'l4casadi' / 'lib')
for lib in manifest:
shutil.copy(lib, file_path / 'l4casadi' / 'lib')

# Copy Header
shutil.copy(file_path / 'libl4casadi' / 'include' / 'l4casadi.hpp', file_path / 'l4casadi' / 'include')
Expand All @@ -39,6 +39,7 @@ def compile_hook(manifest):
'lib/**.dylib',
'lib/**.so',
'lib/**.dll',
'lib/**.lib',
'include/**.hpp',
'template_generation/templates/casadi_function.in.cpp'
]},
Expand Down
6 changes: 6 additions & 0 deletions tests/test_l4casadi.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import numpy as np
import platform
import pytest
import re
import torch
import casadi as cs
import l4casadi as l4c
Expand Down Expand Up @@ -161,6 +163,10 @@ def test_l4casadi_deep_model_hess_double_jac(self):
def test_l4casadi_deep_model_online_update(self, deep_model):
rand_inp = torch.rand((1, deep_model.input_layer.in_features))

if platform.system() == 'Windows':
with pytest.raises(RuntimeError, match=re.escape('Online model update (mutable=True) is not supported on Windows.')):
l4c.L4CasADi(deep_model, mutable=True)
return
l4c_model = l4c.L4CasADi(deep_model, mutable=True)

l4c_out_old = l4c_model(rand_inp.detach().numpy())
Expand Down

0 comments on commit f3681e8

Please sign in to comment.