diff --git a/python/Dockerfile b/python/Dockerfile new file mode 100644 index 000000000..c10788e0e --- /dev/null +++ b/python/Dockerfile @@ -0,0 +1,18 @@ +# Dockerfile to generate PyPI packages. Needs to be run from the opm-common root folder +# Example use: +# sudo docker build -t manylinux2014_opm:built . -f python/Dockerfile + +FROM quay.io/pypa/manylinux_2_28_x86_64 AS stage1 +ARG version_tag="" +ARG build_jobs=4 +WORKDIR /tmp/opm +RUN echo "Using package version tag: $version_tag" +ADD . . +RUN /bin/bash setup-docker-image.sh +FROM stage1 AS stage2 +RUN /bin/bash build-deps.sh $build_jobs +FROM stage2 AS stage3 +RUN /bin/bash generate-pypi-package.sh $version_tag $build_jobs + +FROM scratch AS export_stage +COPY --from=stage3 /tmp/opm/wheelhouse . diff --git a/python/MANIFEST.in b/python/MANIFEST.in new file mode 100644 index 000000000..b02c5e86a --- /dev/null +++ b/python/MANIFEST.in @@ -0,0 +1,2 @@ +include opm/simulators/__init__.py +include opm/simulators/*.so diff --git a/python/build-deps.sh b/python/build-deps.sh new file mode 100755 index 000000000..065880eca --- /dev/null +++ b/python/build-deps.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +set -e + +BUILD_JOBS=$1 + +export CMAKE_GENERATOR=Ninja + +pushd /tmp/opm + +# Build boost +git clone --depth 1 --branch boost-1.84.0 https://github.com/boostorg/boost +pushd boost +git submodule init +git submodule update +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_POSITION_INDEPENDENT_CODE=1 +cmake --build . -- -j${BUILD_JOBS} +cmake --build . --target install +popd + +# Build dune-common +git clone --depth 1 --branch releases/opm/2024.04 https://gitlab.dune-project.org/core/dune-common.git +pushd dune-common +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_POSITION_INDEPENDENT_CODE=1 -DDUNE_ENABLE_PYTHONBINDINGS=0 -DBLA_STATIC=1 -DCMAKE_DISABLE_FIND_PACKAGE_QuadMath=1 -DBLAS_LIBRARIES=/usr/lib64/libblas.a -DLAPACK_LIBRARIES=/usr/lib64/liblapack.a +cmake --build . -- -j${BUILD_JOBS} +cmake --build . --target install +popd + +# Build dune-geometry +git clone --depth 1 --branch v2.9.1 https://gitlab.dune-project.org/core/dune-geometry.git +pushd dune-geometry +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_POSITION_INDEPENDENT_CODE=1 -DDUNE_ENABLE_PYTHONBINDINGS=0 -DBLA_STATIC=1 -DCMAKE_DISABLE_FIND_PACKAGE_QuadMath=1 -DBLAS_LIBRARIES=/usr/lib64/libblas.a -DLAPACK_LIBRARIES=/usr/lib64/liblapack.a +cmake --build . -- -j${BUILD_JOBS} +cmake --build . --target install +popd + +# Build dune-istl +git clone --depth 1 --branch releases/opm/2024.04 https://gitlab.dune-project.org/core/dune-istl.git +pushd dune-istl +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_POSITION_INDEPENDENT_CODE=1 -DDUNE_ENABLE_PYTHONBINDINGS=0 -DBLA_STATIC=1 -DCMAKE_DISABLE_FIND_PACKAGE_QuadMath=1 -DBLAS_LIBRARIES=/usr/lib64/libblas.a -DLAPACK_LIBRARIES=/usr/lib64/liblapack.a +cmake --build . -- -j${BUILD_JOBS} +cmake --build . --target install +popd + +# Build dune-uggrid +git clone --depth 1 --branch v2.9.1 https://gitlab.dune-project.org/staging/dune-uggrid.git +pushd dune-uggrid +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_POSITION_INDEPENDENT_CODE=1 -DDUNE_ENABLE_PYTHONBINDINGS=0 -DBLA_STATIC=1 -DCMAKE_DISABLE_FIND_PACKAGE_QuadMath=1 -DBLAS_LIBRARIES=/usr/lib64/libblas.a -DLAPACK_LIBRARIES=/usr/lib64/liblapack.a +cmake --build . -- -j${BUILD_JOBS} +cmake --build . --target install +popd + +# Build dune-grid +git clone --depth 1 --branch v2.9.1 https://gitlab.dune-project.org/core/dune-grid.git +pushd dune-grid +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_POSITION_INDEPENDENT_CODE=1 -DDUNE_ENABLE_PYTHONBINDINGS=0 -DBLA_STATIC=1 -DCMAKE_DISABLE_FIND_PACKAGE_QuadMath=1 -DBLAS_LIBRARIES=/usr/lib64/libblas.a -DLAPACK_LIBRARIES=/usr/lib64/liblapack.a +cmake --build . -- -j${BUILD_JOBS} +cmake --build . --target install +popd + +# Build dune-localfunctions +git clone --depth 1 --branch v2.9.1 https://gitlab.dune-project.org/core/dune-localfunctions.git +pushd dune-localfunctions +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DCMAKE_POSITION_INDEPENDENT_CODE=1 -DDUNE_ENABLE_PYTHONBINDINGS=0 -DBLA_STATIC=1 -DCMAKE_DISABLE_FIND_PACKAGE_QuadMath=1 -DBLAS_LIBRARIES=/usr/lib64/libblas.a -DLAPACK_LIBRARIES=/usr/lib64/liblapack.a +cmake --build . -- -j${BUILD_JOBS} +cmake --build . --target install +popd + +popd diff --git a/python/generate-pypi-package.sh b/python/generate-pypi-package.sh new file mode 100755 index 000000000..d504a07fb --- /dev/null +++ b/python/generate-pypi-package.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +set -e + +VERSION_TAG=${1:-""} +BUILD_JOBS=$2 + +export CMAKE_GENERATOR=Ninja + +declare -A python_versions +python_versions[cp36-cp36m]=/opt/python/cp36-cp36m/bin/python +python_versions[cp37-cp37m]=/opt/python/cp37-cp37m/bin/python +python_versions[cp38-cp38]=/opt/python/cp38-cp38/bin/python +python_versions[cp39-cp39]=/opt/python/cp39-cp39/bin/python +python_versions[cp310-cp310]=/opt/python/cp310-cp310/bin/python +python_versions[cp311-cp311]=/opt/python/cp311-cp311/bin/python +python_versions[cp312-cp312]=/opt/python/cp312-cp312/bin/python + +for python_bin in ${python_versions[*]} +do + ${python_bin} -m pip install pip --upgrade + ${python_bin} -m pip install wheel setuptools twine pytest-runner auditwheel scikit-build cmake +done + +DIR=`pwd` + +# Setup opm modules +git clone https://github.com/OPM/opm-common +git clone https://github.com/OPM/opm-grid +git clone https://github.com/OPM/opm-models +git clone https://github.com/OPM/opm-simulators +git clone https://github.com/OPM/opm-utilities + +ln -sf opm-utilities/opm-super/CMakeLists.txt CMakeLists.txt +sed -e 's/add_subdirectory(opm-upscaling)//' -e 's/add_dependencies(opmupscaling opmgrid)//g' -i CMakeLists.txt + +mkdir -p /tmp/opm/wheelhouse + +for tag in ${!python_versions[@]} +do + # Build opm-common bindings + pushd opm-common + # Delete the folder if it already exists + if [ -d $tag ]; then + rm -rf $tag + fi + mkdir $tag && cd $tag + cmake -DPYTHON_EXECUTABLE=${python_versions[$tag]} -DWITH_NATIVE=0 \ + -DOPM_ENABLE_PYTHON=ON -DOPM_PYTHON_PACKAGE_VERSION_TAG=${VERSION_TAG} .. + + # make step is necessary until the generated ParserKeywords/*.hpp are generated in the Python step + cmake --build . --target opmcommon_python -- -j${BUILD_JOBS} + cd python + echo -e "include opm/*\ninclude opm/io/summary/__init__.py" > MANIFEST.in + cat MANIFEST.in + ${python_versions[$tag]} setup.py sdist bdist_wheel --plat-name manylinux_2_28_x86_64 --python-tag $tag + ${python_versions[$tag]} -m auditwheel repair dist/*$tag*.whl + cp dist/*$tag*.whl /tmp/opm/wheelhouse + popd + + # Delete the folder if it already exists + if [ -d $tag ]; then + rm -rf $tag + fi + mkdir $tag && pushd $tag + cmake -DPYTHON_EXECUTABLE=${python_versions[$tag]} -DWITH_NATIVE=0 -DBoost_USE_STATIC_LIBS=1 \ + -DOPM_ENABLE_PYTHON=ON -DOPM_PYTHON_PACKAGE_VERSION_TAG=${VERSION_TAG} -DBLA_STATIC=1 -DBLAS_LIBRARIES=/usr/lib64/libblas.a -DSUITESPARSE_USE_STATIC=1 -DCMAKE_DISABLE_FIND_PACKAGE_QuadMath=1 .. + + # make step is necessary until the generated ParserKeywords/*.hpp are generated in the Python step + cmake --build . --target simulators -- -j${BUILD_JOBS} + cd opm-simulators/python + ${python_versions[$tag]} setup.py sdist bdist_wheel --plat-name manylinux_2_28_x86_64 --python-tag $tag + ${python_versions[$tag]} -m auditwheel repair dist/*$tag*.whl + cp dist/*$tag*.whl /tmp/opm/wheelhouse + popd +done diff --git a/python/opm/CMakeLists.txt b/python/opm/CMakeLists.txt new file mode 100644 index 000000000..dc2c0731e --- /dev/null +++ b/python/opm/CMakeLists.txt @@ -0,0 +1,5 @@ +project(install_python_binding) + +cmake_minimum_required(VERSION 3.15) + +install(CODE "message(\"Dummy install\")") diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 000000000..46a1b880b --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,7 @@ +[build-system] +requires= [ + "setuptools>=42", + "scikit-build>=0.13", + "cmake>=3.15", + "ninja" +] diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 000000000..d1a267f64 --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,5 @@ +six +future +decorator +opm +numpy diff --git a/python/setup-docker-image.sh b/python/setup-docker-image.sh new file mode 100755 index 000000000..ed393b619 --- /dev/null +++ b/python/setup-docker-image.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Script to be run on a manylinux2014 docker image to complete it for OPM usage. +# i.e. docker run -i -t quay.io/pypa/manylinux2014_x86_64 < setup-docker-image.sh + +# A ready made Docker image is available at Dockerhub: +# docker run -i -t lindkvis/manylinux2014_opm:latest + +dnf install -y almalinux-release-devel + +dnf install -y blas-static lapack-static suitesparse-static ninja-build diff --git a/python/setup.py.in b/python/setup.py.in new file mode 100644 index 000000000..371212e7c --- /dev/null +++ b/python/setup.py.in @@ -0,0 +1,39 @@ +from skbuild import setup + +import os + +setupdir = os.path.dirname(__file__) +if setupdir != '': + os.chdir( setupdir ) + +with open("README.md", "r") as fh: + long_description = fh.read() + +with open("requirements.txt", "r") as fh: + requires = [line.rstrip() for line in fh] + +setup( + name='opm-simulators', + version = '@opm-simulators_VERSION@' + '@opm-simulators_PYTHON_PACKAGE_VERSION@', + url='http://www.opm-project.org', + author='The Open Porous Media Project', + author_email='opmuser@gmail.com', + description='OPM-Simulators Python bindings', + long_description=long_description, + long_description_content_type="text/markdown", + packages=[ + 'opm', + 'opm.simulators' + ], + package_data={'opm' : ['$']}, + include_package_data=True, + license='Open Source', + test_suite='tests', + setup_requires=["pytest-runner", 'setuptools_scm'], + install_requires=requires, + python_requires='>=3.6', + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + ], +) diff --git a/python/simulators/CMakeLists.txt b/python/simulators/CMakeLists.txt index 76bac1c87..ab65e2d95 100644 --- a/python/simulators/CMakeLists.txt +++ b/python/simulators/CMakeLists.txt @@ -91,6 +91,18 @@ add_custom_target(copy_python ALL ${PROJECT_SOURCE_DIR}/python/test_data ${PROJECT_BINARY_DIR}/python 0 ) +file(COPY ${PROJECT_SOURCE_DIR}/python/opm/CMakeLists.txt DESTINATION ${PROJECT_BINARY_DIR}/python) +file(COPY ${PROJECT_SOURCE_DIR}/python/requirements.txt DESTINATION ${PROJECT_BINARY_DIR}/python) +file(COPY ${PROJECT_SOURCE_DIR}/python/README.md DESTINATION ${PROJECT_BINARY_DIR}/python) +file(COPY ${PROJECT_SOURCE_DIR}/python/MANIFEST.in DESTINATION ${PROJECT_BINARY_DIR}/python) +file(COPY ${PROJECT_SOURCE_DIR}/python/pyproject.toml DESTINATION ${PROJECT_BINARY_DIR}/python) + +# Generate versioned setup.py +configure_file(${PROJECT_SOURCE_DIR}/python/setup.py.in + ${PROJECT_BINARY_DIR}/python/setup.py.tmp @ONLY) +file(GENERATE OUTPUT ${PROJECT_BINARY_DIR}/python/setup.py + INPUT ${PROJECT_BINARY_DIR}/python/setup.py.tmp) + # Since the installation of Python code is nonstandard it is protected by an # extra cmake switch, OPM_INSTALL_PYTHON. If you prefer you can still invoke # setup.py install manually - optionally with the generated script