Skip to content

Commit

Permalink
Add setup.py (#40)
Browse files Browse the repository at this point in the history
Installing the package requires CMake and a C compiler to build the library.
The testing runs on a custom Docker image that includes the dependencies.
  • Loading branch information
edoardob90 authored Feb 2, 2024
1 parent 6dccb53 commit 2a7badf
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 34 deletions.
1 change: 0 additions & 1 deletion .github/workflows/build-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ name: build-docker-image

on:
workflow_dispatch:
pull_request:
push:
branches:
- main
Expand Down
41 changes: 21 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,29 @@ name: continuous-integration
on: [push, pull_request]

jobs:
test-package:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
test-package:
runs-on: ubuntu-latest
container: ghcr.io/${{ github.repository }}/ci-image:latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- name: Check out repository
uses: actions/checkout@v4
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install package and its dependencies
run: pip install -e .[dev]
- name: Install package and its dependencies
run: python3 -m pip install -e .[dev]

- name: Run pytest
run: pytest -v --cov --cov-report json
- name: Run pytest
run: pytest -v --cov --cov-report json

- name: Upload coverage report
uses: codecov/codecov-action@v3
with:
flags: python-${{ matrix.python-version }}
- name: Upload coverage report
uses: codecov/codecov-action@v3
with:
flags: python-${{ matrix.python-version }}
8 changes: 3 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# Use Ubuntu as the base image
FROM ubuntu:latest AS base
FROM alpine:latest AS base

# Install build-essential, cmake, and OpenBLAS
RUN apt-get update && \
apt-get install -y build-essential cmake libopenblas-dev
RUN apk update && \
apk add --no-cache build-base cmake openblas-dev python3
4 changes: 2 additions & 2 deletions cleedpy/cleed/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ set(CMAKE_C_STANDARD 90)
set(CMAKE_C_STANDARD_REQUIRED True)

# Set the output directories
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)

# Where to find CMake modules
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
Expand Down
31 changes: 26 additions & 5 deletions cleedpy/interface/cleed.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import math
from ctypes import POINTER, Structure, c_char_p, c_double, c_int, cdll
import pathlib as pl
import platform
from ctypes import CDLL, POINTER, Structure, c_char_p, c_double, c_int, cdll
from dataclasses import dataclass
from pathlib import Path

from ..config import (
AtomParametersStructured,
Expand Down Expand Up @@ -188,9 +189,29 @@ def generate_crystal_structure(inp: AtomParametersVariants) -> Crystal:
raise ValueError("Not implemented")


def call_cleed():
path = Path(__file__).parent.parent / "cleed" / "build" / "lib" / "libcleed.dylib"
lib = cdll.LoadLibrary(str(path))
def get_cleed_lib() -> CDLL:
lib_path = pl.Path(__file__).parent.parent / "cleed" / "lib"

lib_exts = {
"Windows": ".dll",
"Darwin": ".dylib",
"Linux": ".so",
}

try:
cleed_lib = (
(lib_path / "libcleed").with_suffix(lib_exts[platform.system()]).as_posix()
)
except KeyError as err:
raise ValueError(
f"Platform {platform.system()} not supported by cleedpy"
) from err

return cdll.LoadLibrary(cleed_lib)


def call_cleed() -> None:
lib = get_cleed_lib()

lib.my_test_function.argtypes = [c_int, c_int, POINTER(Crystal)]
lib.my_test_function.restype = c_int
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=61.2"]
requires = ["setuptools>=61.2", "cmake>=3.5"]
build-backend = "setuptools.build_meta"


Expand Down
77 changes: 77 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import os
import pathlib as pl
import platform
import subprocess
import sys

from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext


class CMakeExtension(Extension):
def __init__(self, name, source_dir=""):
Extension.__init__(self, name, sources=[])
self.source_dir = pl.Path(source_dir).absolute()


class CMakeBuild(build_ext):
def run(self):
try:
subprocess.check_output(["cmake", "--version"])
except subprocess.CalledProcessError as err:
raise RuntimeError(
"CMake must be installed to build the following extensions: "
+ ", ".join(e.name for e in self.extensions)
) from err

for ext in self.extensions:
self.build_extension(ext)

def build_extension(self, ext: CMakeExtension):
ext_dir = pl.Path(self.get_ext_fullpath(ext.name)).parent.absolute()

cmake_args = ["-DPYTHON_EXECUTABLE=" + sys.executable]

cfg = "Debug" if self.debug else "Release"
build_args = ["--config", cfg]

if platform.system() == "Windows":
cmake_args += [f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={ext_dir}"]
if sys.maxsize > 2**32:
# We are on a Win 64-bit system
cmake_args += ["-A", "x64"]
build_args += ["--", "/m"]
else:
# These flags are passed as-is to the underlying build tool (make)
build_args += ["--", "-j2"]

env = os.environ.copy()
env["CXXFLAGS"] = '{} -DVERSION_INFO="{}"'.format(
env.get("CXXFLAGS", ""), self.distribution.get_version()
)

build_dir = pl.Path(self.build_temp)
build_dir.mkdir(parents=True, exist_ok=True)

try:
subprocess.check_call(
["cmake", "-S", ext.source_dir.as_posix(), "-B", "."] + cmake_args,
cwd=build_dir,
env=env,
)
subprocess.check_call(["cmake", "--build", "."] + build_args, cwd=build_dir)
except subprocess.CalledProcessError as err:
raise RuntimeError(f"CMake error: {err}") from err


setup(
name="cleedpy",
ext_modules=[
CMakeExtension(
"cleed", (pl.Path(__file__).parent / "cleedpy" / "cleed").as_posix()
)
],
cmdclass={"build_ext": CMakeBuild},
# The following includes *any* library file found in the package directory
package_data={"cleedpy": ["**/*.dylib", "**/*.so", "**/*.dll"]},
)

0 comments on commit 2a7badf

Please sign in to comment.