Skip to content

Commit

Permalink
Merge pull request #14 from diegoferigo/feature/expose_binaries
Browse files Browse the repository at this point in the history
Expose CMake binaries
  • Loading branch information
diegoferigo authored May 28, 2021
2 parents cefa43e + a42742b commit 6363e56
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 12 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ jobs:
shell: bash
continue-on-error: ${{ matrix.experimental }}
run: |
cd examples/swig
pip install -v .
pytest -sv tests
pip install numpy
pip install --no-build-isolation -v ./examples/swig
pytest -sv examples/swig/tests
publish:
name: 'Publish to PyPI'
Expand Down
8 changes: 7 additions & 1 deletion examples/swig/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ target_include_directories(mymath PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)

# =======================
# print_answer executable
# =======================

add_executable(print_answer src/print_answer.cpp)

# =============
# SWIG bindings
# =============
Expand Down Expand Up @@ -77,7 +83,7 @@ set_property(

# Install the target with C++ code
install(
TARGETS mymath
TARGETS mymath print_answer
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
Expand Down
4 changes: 4 additions & 0 deletions examples/swig/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ testing =
pytest-icdiff
all =
%(testing)s

[options.entry_points]
console_scripts =
print_answer = mymath.bin.__main__:main
1 change: 1 addition & 0 deletions examples/swig/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
CMakeExtension(
name="mymath",
install_prefix="mymath",
expose_binaries=["bin/print_answer"],
write_top_level_init=init_py,
source_dir=str(Path(__file__).parent.absolute()),
cmake_configure_options=[
Expand Down
9 changes: 9 additions & 0 deletions examples/swig/src/print_answer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <cstdlib>
#include <iostream>

int main()
{
// Answer to the Ultimate Question of Life, the Universe, and Everything
std::cout << int('*') << std::endl;
return EXIT_SUCCESS;
}
34 changes: 26 additions & 8 deletions examples/swig/tests/test_mymath.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import pytest
import numpy as np
import sys

import mymath.bindings
import numpy as np
import pytest


def test_dot():

v1 = [1., 2, 3, -5.5, 42]
v1 = [1.0, 2, 3, -5.5, 42]
v2 = [-3.2, 0, 13, 6, -3.14]

result = mymath.bindings.dot(vector1=v1, vector2=v2)
Expand All @@ -14,15 +16,15 @@ def test_dot():

def test_normalize():

v = [1., 2, 3, -5.5, 42]
v = [1.0, 2, 3, -5.5, 42]

result = mymath.bindings.normalize(input=v)
assert pytest.approx(result) == np.array(v) / np.linalg.norm(v)


def test_dot_numpy():

v1 = np.array([1., 2, 3, -5.5, 42])
v1 = np.array([1.0, 2, 3, -5.5, 42])
v2 = np.array([-3.2, 0, 13, 6, -3.14])

result = mymath.bindings.dot_numpy(in_1=v1, in_2=v2)
Expand All @@ -31,7 +33,7 @@ def test_dot_numpy():

def test_normalize_numpy():

v = np.array([1., 2, 3, -5.5, 42])
v = np.array([1.0, 2, 3, -5.5, 42])

result = mymath.bindings.normalize_numpy(in_1=v)

Expand All @@ -41,8 +43,24 @@ def test_normalize_numpy():

def test_assertion():

v1 = np.array([1., 2, 3, -5.5])
v2 = np.array([42.])
v1 = np.array([1.0, 2, 3, -5.5])
v2 = np.array([42.0])

with pytest.raises(RuntimeError):
_ = mymath.bindings.dot_numpy(in_1=v1, in_2=v2)


@pytest.mark.skipif(
sys.version_info < (3, 7), reason="capture_output and text require Python 3.7"
)
def test_executable():

import subprocess

result = subprocess.run("print_answer", capture_output=True, text=True)
assert result.stdout.strip() == "42"

result = subprocess.run(
"python -m mymath.bin print_answer".split(), capture_output=True, text=True
)
assert result.stdout.strip() == "42"
51 changes: 51 additions & 0 deletions src/cmake_build_extension/build_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import platform
import shutil
import subprocess
import sys
from pathlib import Path

from setuptools.command.build_ext import build_ext
Expand Down Expand Up @@ -181,6 +182,56 @@ def build_extension(self, ext: CMakeExtension) -> None:
with open(file=cmake_install_prefix / "__init__.py", mode="w") as f:
f.write(ext.write_top_level_init)

# Write content to the bin/__main__.py magic file to expose binaries
if len(ext.expose_binaries) > 0:
bin_dirs = {str(Path(d).parents[0]) for d in ext.expose_binaries}

import inspect

main_py = inspect.cleandoc(
f"""
from pathlib import Path
import subprocess
import sys
def main():
binary_name = Path(sys.argv[0]).name
prefix = Path(__file__).parent.parent
bin_dirs = {str(bin_dirs)}
binary_path = ""
for dir in bin_dirs:
path = prefix / Path(dir) / binary_name
if path.is_file():
binary_path = str(path)
break
path = Path(str(path) + ".exe")
if path.is_file():
binary_path = str(path)
break
if not Path(binary_path).is_file():
name = binary_path if binary_path != "" else binary_name
raise RuntimeError(f"Failed to find binary: {{ name }}")
sys.argv[0] = binary_path
result = subprocess.run(args=sys.argv, capture_output=False)
exit(result.returncode)
if __name__ == "__main__" and len(sys.argv) > 1:
sys.argv = sys.argv[1:]
main()"""
)

bin_folder = cmake_install_prefix / "bin"
Path(bin_folder).mkdir(exist_ok=True, parents=True)
with open(file=bin_folder / "__main__.py", mode="w") as f:
f.write(main_py)

@staticmethod
def extend_cmake_prefix_path(path: str) -> None:

Expand Down
3 changes: 3 additions & 0 deletions src/cmake_build_extension/cmake_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CMakeExtension(Extension):
cmake_build_type: The default build type of the CMake project.
cmake_component: The name of component to install. Defaults to all components.
cmake_depends_on: List of dependency packages containing required CMake projects.
expose_binaries: List of binary paths to expose, relative to top-level directory.
"""

def __init__(
Expand All @@ -33,6 +34,7 @@ def __init__(
cmake_build_type: str = "Release",
cmake_component: str = None,
cmake_depends_on: List[str] = (),
expose_binaries: List[str] = (),
):

super().__init__(name=name, sources=[])
Expand All @@ -51,3 +53,4 @@ def __init__(
self.source_dir = str(Path(source_dir).absolute())
self.cmake_configure_options = cmake_configure_options
self.cmake_component = cmake_component
self.expose_binaries = expose_binaries

0 comments on commit 6363e56

Please sign in to comment.