Skip to content

Commit 3f086d7

Browse files
Merge pull request #915 from PowerGridModel/feature/conda-build-cmake
Use cmake to build the DLL for conda package
2 parents bb3d99b + 8701c4b commit 3f086d7

File tree

4 files changed

+67
-39
lines changed

4 files changed

+67
-39
lines changed

.github/conda_pgm_env.yml

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ dependencies:
1515
- nlohmann_json
1616
- msgpack-cxx
1717
- numpy
18+
- cmake
19+
- ninja
1820
# test deps
1921
- pytest
2022
- pytest-cov

.github/workflows/main.yml

+30-4
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,18 @@ jobs:
235235
strategy:
236236
matrix:
237237
os: ["ubuntu", "windows"] # We do not test conda for MacOS
238-
238+
include:
239+
- os: ubuntu
240+
shell: bash -el {0}
241+
- os: windows
242+
shell: powershell
243+
244+
env:
245+
POWER_GRID_MODEL_NO_BINARY_BUILD: 1
246+
239247
defaults:
240248
run:
241-
shell: bash -el {0}
249+
shell: ${{ matrix.shell }}
242250
steps:
243251
- uses: actions/checkout@v4
244252

@@ -253,8 +261,26 @@ jobs:
253261
run: |
254262
conda info
255263
conda list
256-
257-
- name: Build
264+
265+
- name: Build and install cmake target for Windows
266+
if: matrix.os == 'windows'
267+
run: |
268+
$vsPath = &(Join-Path ${env:ProgramFiles(x86)} '\Microsoft Visual Studio\Installer\vswhere.exe') -property installationpath
269+
Import-Module (Join-Path $vsPath 'Common7\Tools\Microsoft.VisualStudio.DevShell.dll')
270+
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation -DevCmdArguments '-arch=x64 -host_arch=x64'
271+
272+
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -B build/ -S .
273+
cmake --build build/ --verbose -j1
274+
cmake --install build/ --prefix ${env:CONDA_PREFIX}/Library
275+
276+
- name: Build and install cmake target for Linux
277+
if: matrix.os == 'ubuntu'
278+
run: |
279+
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -B build/ -S . -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX -DCMAKE_PREFIX_PATH=$CONDA_PREFIX
280+
cmake --build build/ --verbose -j1
281+
cmake --install build/
282+
283+
- name: Build python
258284
run: python -m pip install . -vv --no-build-isolation --no-deps
259285

260286
- name: Test

setup.py

+17-34
Original file line numberDiff line numberDiff line change
@@ -55,28 +55,6 @@ def get_pre_installed_header_include() -> list[str]:
5555
return []
5656

5757

58-
def get_conda_include() -> list[str]:
59-
"""
60-
Get conda include path, if we are inside conda environment
61-
62-
Returns:
63-
either empty list or a list of header paths
64-
"""
65-
include_paths = []
66-
# in the conda build system the system root is defined in CONDA_PREFIX or BUILD_PREFIX
67-
for prefix in ["CONDA_PREFIX", "BUILD_PREFIX"]:
68-
if prefix in os.environ:
69-
conda_path = os.environ[prefix]
70-
if if_win:
71-
# windows has Library folder prefix
72-
include_paths.append(os.path.join(conda_path, "Library", "include"))
73-
include_paths.append(os.path.join(conda_path, "Library", "include", "eigen3"))
74-
else:
75-
include_paths.append(os.path.join(conda_path, "include"))
76-
include_paths.append(os.path.join(conda_path, "include", "eigen3"))
77-
return include_paths
78-
79-
8058
# custom class for ctypes
8159
class CTypesExtension(Extension):
8260
pass
@@ -150,6 +128,23 @@ def generate_build_ext(pkg_dir: Path, pkg_name: str):
150128
Returns:
151129
152130
"""
131+
pkg_bin_dir = pkg_dir / "src" / pkg_name
132+
# remove old extension build
133+
build_dir = pkg_dir / "build"
134+
if build_dir.exists():
135+
shutil.rmtree(build_dir)
136+
# remove binary
137+
bin_files = list(chain(pkg_bin_dir.rglob("*.so"), pkg_bin_dir.rglob("*.dll"), pkg_bin_dir.rglob("*.dylib")))
138+
for bin_file in bin_files:
139+
print(f"Remove binary file: {bin_file}")
140+
bin_file.unlink()
141+
142+
# By setting POWER_GRID_MODEL_NO_BINARY_BUILD we do not build the extension.
143+
# This is usually set in conda-build recipe, so conda build process only wraps the pure Python package.
144+
# As a user or developer, DO NOT set this environment variable unless you really know what you are doing.
145+
if "POWER_GRID_MODEL_NO_BINARY_BUILD" in os.environ:
146+
return {}
147+
153148
# fetch dependent headers
154149
pgm = Path("power_grid_model")
155150
pgm_c = Path("power_grid_model_c")
@@ -161,7 +156,6 @@ def generate_build_ext(pkg_dir: Path, pkg_name: str):
161156
]
162157
include_dirs += get_required_dependency_include()
163158
include_dirs += get_pre_installed_header_include()
164-
include_dirs += get_conda_include()
165159
# compiler and link flag
166160
cflags: list[str] = []
167161
lflags: list[str] = []
@@ -179,17 +173,6 @@ def generate_build_ext(pkg_dir: Path, pkg_name: str):
179173
define_macros = [
180174
("EIGEN_MPL2_ONLY", "1"), # only MPL-2 part of eigen3
181175
]
182-
pkg_bin_dir = pkg_dir / "src" / pkg_name
183-
184-
# remove old extension build
185-
build_dir = pkg_dir / "build"
186-
if build_dir.exists():
187-
shutil.rmtree(build_dir)
188-
# remove binary
189-
bin_files = list(chain(pkg_bin_dir.rglob("*.so"), pkg_bin_dir.rglob("*.dll")))
190-
for bin_file in bin_files:
191-
print(f"Remove binary file: {bin_file}")
192-
bin_file.unlink()
193176

194177
# build steps for Windows and Linux
195178
# different treat for windows and linux

src/power_grid_model/_core/power_grid_core.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Loader for the dynamic library
77
"""
88

9+
import os
910
import platform
1011
from ctypes import CDLL, POINTER, c_char, c_char_p, c_double, c_size_t, c_void_p
1112
from inspect import signature
@@ -125,11 +126,27 @@ def _load_core() -> CDLL:
125126
Returns: DLL/SO object
126127
127128
"""
129+
# first try to find the DLL local
128130
if platform.system() == "Windows":
129131
dll_file = "_power_grid_core.dll"
130132
else:
131133
dll_file = "_power_grid_core.so"
132-
cdll = CDLL(str(Path(__file__).parent / dll_file))
134+
dll_path = Path(__file__).parent / dll_file
135+
136+
# if local DLL is not found, try to find the DLL from conda environment
137+
if (not dll_path.exists()) and ("CONDA_PREFIX" in os.environ):
138+
if platform.system() == "Windows":
139+
dll_file = "power_grid_model_c.dll"
140+
elif platform.system() == "Darwin":
141+
dll_file = "libpower_grid_model_c.dylib"
142+
elif platform.system() == "Linux":
143+
dll_file = "libpower_grid_model_c.so"
144+
else:
145+
raise NotImplementedError(f"Unsupported platform: {platform.system()}")
146+
# the dll will be found through conda environment
147+
dll_path = Path(dll_file)
148+
149+
cdll = CDLL(str(dll_path))
133150
# assign return types
134151
# handle
135152
cdll.PGM_create_handle.argtypes = []

0 commit comments

Comments
 (0)