diff --git a/.gitignore b/.gitignore index 1c8d2d8653f..d3e5862f91c 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,9 @@ CMakeUserPresets.json nohup.out /Documentation/.vale + +# Python build +/_skbuild +*.egg-info/ +/wheelhouse .ipynb_checkpoints diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e4fe9bb3d18..b92c02dcc36 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,6 +37,7 @@ include: - local: "/scripts/ci/jobs/build-win.yml" - local: "/scripts/ci/jobs/build-mac.yml" - local: "/scripts/ci/jobs/build-container.yml" + - local: "/scripts/ci/jobs/build-wheels.yml" - local: "/scripts/ci/jobs/jupyter.yml" - local: "/scripts/ci/jobs/code-quality.yml" - local: "/scripts/ci/jobs/code-coverage.yml" diff --git a/Applications/CLI/CMakeLists.txt b/Applications/CLI/CMakeLists.txt index 742e4be2a96..4d221c96d5d 100644 --- a/Applications/CLI/CMakeLists.txt +++ b/Applications/CLI/CMakeLists.txt @@ -45,31 +45,6 @@ if(OGS_USE_PYTHON) $<$:PRIVATE OGS_BUILD_SHARED_LIBS> ) - - # Create OpenGeoSys python module - # https://pybind11.readthedocs.io/en/stable/compiling.html#building-with-cmake - if(OGS_BUILD_PYTHON_MODULE) - pybind11_add_module( - OpenGeoSys MODULE ogs_python_module.cpp - CommandLineArgumentParser.cpp - ) - - # lld linker strips out PyInit_OpenGeoSys symbol. Use standard linker. - get_target_property(_link_options OpenGeoSys LINK_OPTIONS) - if(_link_options) - list(REMOVE_ITEM _link_options -fuse-ld=lld) - set_target_properties( - OpenGeoSys PROPERTIES LINK_OPTIONS "${_link_options}" - ) - endif() - - target_link_libraries( - OpenGeoSys PRIVATE ApplicationsLib BaseLib GitInfoLib tclap - pybind11::pybind11 - ) - - install(TARGETS OpenGeoSys LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) - endif() endif() ogs_add_executable(ogs ogs.cpp CommandLineArgumentParser.cpp) @@ -94,28 +69,6 @@ target_link_libraries( add_test(NAME ogs_no_args COMMAND ogs) set_tests_properties(ogs_no_args PROPERTIES WILL_FAIL TRUE LABELS "default") -if(OGS_BUILD_PYTHON_MODULE AND NOT (WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL - "Release") -) - # TODO: does not work on Windows Release - add_test( - NAME ogs_python_module - COMMAND - ${Python_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/ogs_python_module.py - ${CMAKE_SOURCE_DIR}/Tests/Data/Parabolic/LiquidFlow/Flux/cube_1e3_calculatesurfaceflux.prj - ) - set_tests_properties( - ogs_python_module - PROPERTIES - LABELS - "default" - ENVIRONMENT_MODIFICATION - PYTHONPATH=path_list_append:$:${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} - DISABLED - $> - ) -endif() # ---- Installation ---- install(TARGETS ogs RUNTIME DESTINATION bin) diff --git a/Applications/CLI/ogs_python_module.py b/Applications/CLI/ogs_python_module.py deleted file mode 100755 index 8f553584fdd..00000000000 --- a/Applications/CLI/ogs_python_module.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 - -# The OpenGeoSys python library will be created in the lib folder of the build -# directory -## export PYTHONPATH=$PYTHONPATH:your-build_directory-here/lib - -import sys -import tempfile -import OpenGeoSys - -arguments = ["", sys.argv[1], "-o " + tempfile.mkdtemp()] - -print("Python OpenGeoSys.init ...") -OpenGeoSys.initialize(arguments) -print("Python OpenGeoSys.executeSimulation ...") -OpenGeoSys.executeSimulation() -print("Python OpenGeoSys.finalize() ...") -OpenGeoSys.finalize() -print("Python world.") diff --git a/Applications/CMakeLists.txt b/Applications/CMakeLists.txt index 386e8b3f40d..ce44d2681a9 100644 --- a/Applications/CMakeLists.txt +++ b/Applications/CMakeLists.txt @@ -19,3 +19,7 @@ endif() # OGS_BUILD_CLI if(OGS_USE_INSITU) add_subdirectory(InSituLib) endif() + +if(OGS_BUILD_PYTHON_MODULE) + add_subdirectory(Python) +endif() diff --git a/Applications/Python/CMakeLists.txt b/Applications/Python/CMakeLists.txt new file mode 100644 index 00000000000..667d1aff703 --- /dev/null +++ b/Applications/Python/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ogs.simulator) diff --git a/Applications/Python/ogs.simulator/CMakeLists.txt b/Applications/Python/ogs.simulator/CMakeLists.txt new file mode 100644 index 00000000000..dce167cd59f --- /dev/null +++ b/Applications/Python/ogs.simulator/CMakeLists.txt @@ -0,0 +1,21 @@ +# Create OpenGeoSys python module +# https://pybind11.readthedocs.io/en/stable/compiling.html#building-with-cmake +pybind11_add_module( + simulator MODULE ogs_python_module.cpp + ../../CLI/CommandLineArgumentParser.cpp +) + +# lld linker strips out PyInit_OpenGeoSys symbol. Use standard linker. +get_target_property(_link_options simulator LINK_OPTIONS) +if(_link_options) + list(REMOVE_ITEM _link_options -fuse-ld=lld) + set_target_properties(simulator PROPERTIES LINK_OPTIONS "${_link_options}") +endif() + +target_link_libraries( + simulator PRIVATE ApplicationsLib BaseLib CMakeInfoLib GitInfoLib tclap +) +target_include_directories(simulator PRIVATE ../../CLI) + +# Install into root dir (in Python module, enables 'import ogs.simulator') +install(TARGETS simulator LIBRARY DESTINATION .) diff --git a/Applications/CLI/ogs_python_module.cpp b/Applications/Python/ogs.simulator/ogs_python_module.cpp similarity index 98% rename from Applications/CLI/ogs_python_module.cpp rename to Applications/Python/ogs.simulator/ogs_python_module.cpp index 9abbe7a727d..ab4103c7c65 100644 --- a/Applications/CLI/ogs_python_module.cpp +++ b/Applications/Python/ogs.simulator/ogs_python_module.cpp @@ -122,8 +122,9 @@ void finalize() } /// python module name is OpenGeoSys -PYBIND11_MODULE(OpenGeoSys, m) +PYBIND11_MODULE(simulator, m) { + m.attr("__name__") = "ogs.simulator"; m.doc() = "pybind11 ogs example plugin"; m.def("initialize", &initOGS, "init OGS"); m.def("currentTime", ¤tTime, "get current OGS time"); diff --git a/Applications/Python/ogs/__init__.py b/Applications/Python/ogs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Applications/Python/ogs/_internal/__init__.py b/Applications/Python/ogs/_internal/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Applications/Python/ogs/_internal/provide_ogs_cli_tools_via_wheel.py b/Applications/Python/ogs/_internal/provide_ogs_cli_tools_via_wheel.py new file mode 100644 index 00000000000..ea18d343753 --- /dev/null +++ b/Applications/Python/ogs/_internal/provide_ogs_cli_tools_via_wheel.py @@ -0,0 +1,80 @@ +import os +import platform +import subprocess +import sys + +binaries_list = [ + "addDataToRaster", + "AddElementQuality", + "AddFaultToVoxelGrid", + "AddLayer", + "appendLinesAlongPolyline", + "AssignRasterDataToMesh", + "checkMesh", + "ComputeNodeAreasFromSurfaceMesh", + "computeSurfaceNodeIDsInPolygonalRegion", + "constructMeshesFromGeometry", + "convertGEO", + "convertToLinearMesh", + "convertVtkDataArrayToVtkDataArray", + "CreateBoundaryConditionsAlongPolylines", + "createIntermediateRasters", + "createLayeredMeshFromRasters", + "createMeshElemPropertiesFromASCRaster", + "createNeumannBc", + "createQuadraticMesh", + "createRaster", + "editMaterialID", + "ExtractBoundary", + "ExtractMaterials", + "ExtractSurface", + "generateGeometry", + "generateMatPropsFromMatID", + "generateStructuredMesh", + "geometryToGmshGeo", + "GMSH2OGS", + "GocadSGridReader", + "GocadTSurfaceReader", + "identifySubdomains", + "IntegrateBoreholesIntoMesh", + "Layers2Grid", + "MapGeometryToMeshSurface", + "Mesh2Raster", + "MoveGeometry", + "MoveMesh", + "moveMeshNodes", + "mpmetis", + "NodeReordering", + "ogs", + "OGS2VTK", + "partmesh", + "PVD2XDMF", + "queryMesh", + "Raster2Mesh", + "RemoveGhostData", + "removeMeshElements", + "ResetPropertiesInPolygonalRegion", + "reviseMesh", + "scaleProperty", + "swapNodeCoordinateAxes", + "TecPlotTools", + "tetgen", + "TIN2VTK", + "VTK2OGS", + "VTK2TIN", + "vtkdiff", + "Vtu2Grid", +] + +if "PEP517_BUILD_BACKEND" not in os.environ: + OGS_BIN_DIR = os.path.join(os.path.join(os.path.dirname(__file__), "..", "bin")) + + if platform.system() == "Windows": + os.add_dll_directory(OGS_BIN_DIR) + + def _program(name, args): + return subprocess.run([os.path.join(OGS_BIN_DIR, name)] + args).returncode + + FUNC_TEMPLATE = """def {0}(): raise SystemExit(_program("{0}", sys.argv[1:]))""" + for f in binaries_list: + exec(FUNC_TEMPLATE.format(f)) diff --git a/Applications/Utils/GeoTools/addDataToRaster.cpp b/Applications/Utils/GeoTools/addDataToRaster.cpp index f21fc93f952..92e3ef9d337 100644 --- a/Applications/Utils/GeoTools/addDataToRaster.cpp +++ b/Applications/Utils/GeoTools/addDataToRaster.cpp @@ -24,6 +24,7 @@ #include "GeoLib/AABB.h" #include "GeoLib/Point.h" #include "GeoLib/Raster.h" +#include "InfoLib/GitInfo.h" double compute2DGaussBellCurveValues(GeoLib::Point const& point, GeoLib::AABB const& aabb) @@ -51,7 +52,14 @@ double computeSinXSinY(GeoLib::Point const& point, GeoLib::AABB const& aabb) int main(int argc, char* argv[]) { - TCLAP::CmdLine cmd("Add values to raster.", ' ', "0.1"); + TCLAP::CmdLine cmd( + "Add values to raster.\n\n" + "OpenGeoSys-6 software, version " + + GitInfoLib::GitInfo::ogs_version + + ".\n" + "Copyright (c) 2012-2022, OpenGeoSys Community " + "(http://www.opengeosys.org)", + ' ', GitInfoLib::GitInfo::ogs_version); TCLAP::ValueArg out_raster_arg( "o", diff --git a/Applications/Utils/GeoTools/createRaster.cpp b/Applications/Utils/GeoTools/createRaster.cpp index d58e7c29036..c9a521c53d5 100644 --- a/Applications/Utils/GeoTools/createRaster.cpp +++ b/Applications/Utils/GeoTools/createRaster.cpp @@ -20,11 +20,18 @@ #include "GeoLib/AABB.h" #include "GeoLib/Point.h" #include "GeoLib/Raster.h" +#include "InfoLib/GitInfo.h" int main(int argc, char* argv[]) { - TCLAP::CmdLine cmd("Create a raster where every pixel is zero.", ' ', - "0.1"); + TCLAP::CmdLine cmd( + "computeSurfaceNodeIDsInPolygonalRegion\n\n" + "OpenGeoSys-6 software, version " + + GitInfoLib::GitInfo::ogs_version + + ".\n" + "Copyright (c) 2012-2022, OpenGeoSys Community " + "(http://www.opengeosys.org)", + ' ', GitInfoLib::GitInfo::ogs_version); TCLAP::ValueArg output_arg("o", "output", "Name of the output raster (*.asc)", diff --git a/Applications/Utils/MeshEdit/appendLinesAlongPolyline.cpp b/Applications/Utils/MeshEdit/appendLinesAlongPolyline.cpp index 0774cc64529..1b6625b70fe 100644 --- a/Applications/Utils/MeshEdit/appendLinesAlongPolyline.cpp +++ b/Applications/Utils/MeshEdit/appendLinesAlongPolyline.cpp @@ -50,7 +50,7 @@ int main(int argc, char* argv[]) "the name of the geometry file"); cmd.add(geoFileArg); - TCLAP::ValueArg gmsh_path_arg("g", "gmsh-path", + TCLAP::ValueArg gmsh_path_arg("", "gmsh-path", "the path to the gmsh binary", false, "", "path as string"); cmd.add(gmsh_path_arg); diff --git a/Applications/Utils/MeshGeoTools/computeSurfaceNodeIDsInPolygonalRegion.cpp b/Applications/Utils/MeshGeoTools/computeSurfaceNodeIDsInPolygonalRegion.cpp index b628eceb948..355c00e3269 100644 --- a/Applications/Utils/MeshGeoTools/computeSurfaceNodeIDsInPolygonalRegion.cpp +++ b/Applications/Utils/MeshGeoTools/computeSurfaceNodeIDsInPolygonalRegion.cpp @@ -94,7 +94,7 @@ int main(int argc, char* argv[]) true, "", "file name of input geometry"); cmd.add(geo_in); - TCLAP::ValueArg gmsh_path_arg("g", "gmsh-path", + TCLAP::ValueArg gmsh_path_arg("", "gmsh-path", "the path to the gmsh binary", false, "", "path as string"); cmd.add(gmsh_path_arg); diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c4027cd17f..556122f6a8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,12 +4,9 @@ cmake_minimum_required(VERSION 3.22) project(OGS-6) option(OGS_USE_PYTHON "Interface with Python" ON) -include(CMakeDependentOption) -cmake_dependent_option( - OGS_BUILD_PYTHON_MODULE "Should the OGS Python module be built?" ON - "OGS_USE_PYTHON" OFF -) +option(OGS_BUILD_PYTHON_MODULE "Should the OGS Python module be built?" ON) +include(CMakeDependentOption) include(scripts/cmake/DownloadCpmCache.cmake) include(scripts/cmake/CPM.cmake) include(scripts/cmake/CMakeSetup.cmake) diff --git a/CMakePresets.json b/CMakePresets.json index 703fc056a2c..583bde18d31 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -187,6 +187,39 @@ "OGS_USE_PYTHON": "OFF", "OGS_USE_UNITY_BUILDS": "OFF" } + }, + { + "name": "wheel", + "inherits": "release", + "cacheVariables": { + "HDF5_USE_STATIC_LIBRARIES": "ON", + "OGS_BUILD_HDF5": "ON", + "OGS_USE_PYTHON": "OFF", + "OGS_BUILD_PYTHON_MODULE": "ON", + "OGS_BUILD_TESTING": "OFF", + "OGS_INSTALL_DEPENDENCIES": "OFF", + "OGS_USE_PIP": "OFF", + "OGS_USE_MFRONT": "ON", + "BUILD_SHARED_LIBS": "ON" + }, + "condition": { + "type": "notEquals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "wheel-win", + "inherits": "wheel", + "cacheVariables": { + "OGS_USE_MFRONT": "OFF", + "OGS_BUILD_PROCESS_TH2M": "OFF" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } } ], "buildPresets": [ @@ -270,6 +303,14 @@ { "name": "ci-no-unity", "configurePreset": "ci-no-unity" + }, + { + "name": "wheel", + "configurePreset": "wheel" + }, + { + "name": "wheel-win", + "configurePreset": "wheel-win" } ], "testPresets": [ diff --git a/MaterialLib/SolidModels/Ehlers.cpp b/MaterialLib/SolidModels/Ehlers.cpp index 28adfbd6cae..83995b28955 100644 --- a/MaterialLib/SolidModels/Ehlers.cpp +++ b/MaterialLib/SolidModels/Ehlers.cpp @@ -842,6 +842,9 @@ SolidEhlers::getInternalVariables() const template class SolidEhlers<2>; template class SolidEhlers<3>; +template struct StateVariables<2>; +template struct StateVariables<3>; + template <> MathLib::KelvinVector::KelvinMatrixType<3> sOdotS<3>( MathLib::KelvinVector::KelvinVectorType<3> const& v) diff --git a/MaterialLib/SolidModels/Ehlers.h b/MaterialLib/SolidModels/Ehlers.h index 9c9d3ec2bfb..d984878cf13 100644 --- a/MaterialLib/SolidModels/Ehlers.h +++ b/MaterialLib/SolidModels/Ehlers.h @@ -24,11 +24,10 @@ #include "BaseLib/Error.h" #include "MathLib/KelvinVector.h" +#include "MechanicsBase.h" #include "NumLib/NewtonRaphson.h" #include "ParameterLib/Parameter.h" -#include "MechanicsBase.h" - namespace MaterialLib { namespace Solids @@ -250,8 +249,8 @@ struct StateVariables Damage damage_prev; ///< \copydoc damage #ifndef NDEBUG - friend std::ostream& operator<<( - std::ostream& os, StateVariables const& m) + friend std::ostream& operator<<(std::ostream& os, + StateVariables const& m) { os << "State:\n" << "eps_p_D: " << m.eps_p.D << "\n" @@ -358,6 +357,9 @@ class SolidEhlers final : public MechanicsBase extern template class SolidEhlers<2>; extern template class SolidEhlers<3>; + +extern template struct StateVariables<2>; +extern template struct StateVariables<3>; } // namespace Ehlers } // namespace Solids } // namespace MaterialLib diff --git a/Tests/Python/__init__.py b/Tests/Python/__init__.py new file mode 100644 index 00000000000..fd1659acb19 --- /dev/null +++ b/Tests/Python/__init__.py @@ -0,0 +1,11 @@ +import sys + +from contextlib import contextmanager + + +@contextmanager +def push_argv(argv): + old_argv = sys.argv + sys.argv = argv + yield + sys.argv = old_argv diff --git a/Tests/Python/test_cli.py b/Tests/Python/test_cli.py new file mode 100644 index 00000000000..c9d23bb8d79 --- /dev/null +++ b/Tests/Python/test_cli.py @@ -0,0 +1,23 @@ +import tempfile +import os + +import pytest + +import ogs._internal.provide_ogs_cli_tools_via_wheel as ogs_cli_wheel + +from . import push_argv + + +def _run(program, args): + func = getattr(ogs_cli_wheel, program) + args = ["%s.py" % program] + args + with push_argv(args), pytest.raises(SystemExit) as excinfo: + func() + assert 0 == excinfo.value.code + + +def test_binaries(): + ignore_list = ["moveMeshNodes", "mpmetis", "tetgen"] # have no --version cli flag + for f in ogs_cli_wheel.binaries_list: + if f not in ignore_list: + _run(f, ["--version"]) diff --git a/Tests/Python/test_simlator.py b/Tests/Python/test_simlator.py new file mode 100644 index 00000000000..bb41cad5e08 --- /dev/null +++ b/Tests/Python/test_simlator.py @@ -0,0 +1,20 @@ +import tempfile +import os + +import pytest +import ogs.simulator as sim + + +def test_simulator(): + arguments = [ + "", + f"{os.path.abspath(os.path.dirname(__file__))}/../Data/Parabolic/LiquidFlow/Flux/cube_1e3_calculatesurfaceflux.prj", + "-o " + tempfile.mkdtemp(), + ] + + print("Python OpenGeoSys.init ...") + sim.initialize(arguments) + print("Python OpenGeoSys.executeSimulation ...") + sim.executeSimulation() + print("Python OpenGeoSys.finalize() ...") + sim.finalize() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..5dc4a1ce709 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[build-system] +requires = [ + "setuptools>=42", + "scikit-build @ git+https://github.com/bilke/scikit-build/@disable-cmake-install-check#egg=scikit-build ; platform_system == 'Windows'", + "scikit-build>=0.15.0 ; platform_system != 'Windows'", + "cmake>=3.22", + "ninja ; platform_system != 'Windows'", +] +build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +testpaths = ["Tests"] +norecursedirs = ["Tests/Data"] + +[tool.cibuildwheel] +archs = "auto64" +build = "cp3*" +test-extras = "test" +test-command = "pytest {project}/Tests/Python" +build-verbosity = "1" + +[tool.cibuildwheel.linux] +skip = ["*musllinux*", "cp36-*"] +manylinux-x86_64-image = "manylinux2014" +manylinux-aarch64-image = "manylinux2014" +environment-pass = ["OGS_VERSION"] + +[tool.cibuildwheel.macos] +skip = ["cp36-*", "cp37-*", "cp38-*x86_64"] + +[tool.cibuildwheel.windows] +skip = ["cp36-*", "cp37-*"] diff --git a/scripts/ci/jobs/build-wheels.yml b/scripts/ci/jobs/build-wheels.yml new file mode 100644 index 00000000000..b43eb0402b7 --- /dev/null +++ b/scripts/ci/jobs/build-wheels.yml @@ -0,0 +1,32 @@ +.wheels_template: &wheels_template + stage: build + needs: [meta] + script: + - pipx run cibuildwheel + rules: + - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH + - if: $CI_COMMIT_TAG + artifacts: + paths: + - wheelhouse/ + +build wheels linux: + tags: [envinf3-shell] + <<: *wheels_template + +build wheels mac: + tags: + - mac + - ${ARCHITECTURE} + variables: + CMAKE_OSX_DEPLOYMENT_TARGET: "10.15" + parallel: + matrix: + - ARCHITECTURE: ["amd64", "arm64"] + <<: *wheels_template + +build wheels win: + tags: [windows] + extends: + - .vs2019-environment + <<: *wheels_template diff --git a/scripts/ci/jobs/jupyter.yml b/scripts/ci/jobs/jupyter.yml index 875e30ff859..039cd85185c 100644 --- a/scripts/ci/jobs/jupyter.yml +++ b/scripts/ci/jobs/jupyter.yml @@ -1,7 +1,7 @@ # Built for Sandy Bridge (envinf1) and newer build jupyter: stage: build - tags: [envinf, shell] + tags: [envinf23, shell] needs: [meta] extends: - .container-maker-setup diff --git a/scripts/ci/jobs/pre-commit.yml b/scripts/ci/jobs/pre-commit.yml index 292e826aeab..10672766a16 100644 --- a/scripts/ci/jobs/pre-commit.yml +++ b/scripts/ci/jobs/pre-commit.yml @@ -20,7 +20,7 @@ clang-format: needs: [ci_images] allow_failure: true script: - - git clang-format ${CI_MERGE_REQUEST_TARGET_BRANCH_SHA} + - git clang-format --extensions "h,cpp" ${CI_MERGE_REQUEST_TARGET_BRANCH_SHA} - if [[ $(git diff) ]]; then exit 1; fi after_script: - git diff diff --git a/scripts/ci/jobs/release.yml b/scripts/ci/jobs/release.yml index 81bba47813a..bb2581ec1ca 100644 --- a/scripts/ci/jobs/release.yml +++ b/scripts/ci/jobs/release.yml @@ -9,3 +9,24 @@ release: release: tag_name: "$CI_COMMIT_TAG" description: "Created using the GitLab release-cli." + +publish wheels: + stage: release + needs: ["build wheels linux", "build wheels mac", "build wheels win"] + tags: [envinf, shell] + rules: + - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH + variables: + PYPI_PASSWORD: "${TEST_PYPI_TOKEN}" + PYPI_REPO: testpypi + - if: $CI_COMMIT_TAG + variables: + PYPI_PASSWORD: "${PYPI_TOKEN}" + PYPI_REPO: pypi + script: + - > + pipx run twine upload + --repository ${PYPI_REPO} + --username __token__ + --password ${PYPI_PASSWORD} + wheelhouse/* diff --git a/scripts/cmake/Dependencies.cmake b/scripts/cmake/Dependencies.cmake index 5c174b73f02..f891daaf0e8 100644 --- a/scripts/cmake/Dependencies.cmake +++ b/scripts/cmake/Dependencies.cmake @@ -87,10 +87,9 @@ if(tetgen_ADDED) list(APPEND DISABLE_WARNINGS_TARGETS tet tetgen) endif() -if(OGS_USE_PYTHON) +if(OGS_USE_PYTHON OR OGS_BUILD_PYTHON_MODULE) CPMAddPackage( - NAME pybind11 GITHUB_REPOSITORY pybind/pybind11 - GIT_TAG f1abf5d9159b805674197f6bc443592e631c9130 + NAME pybind11 GITHUB_REPOSITORY pybind/pybind11 VERSION 2.10.0 ) endif() @@ -182,6 +181,7 @@ if(OGS_BUILD_SWMM) CPMAddPackage( NAME SWMMInterface GITHUB_REPOSITORY ufz/SwmmInterface GIT_TAG 141e05ae1f419918799d7bf9178ebcd97feb1ed3 + OPTIONS "BUILD_SHARED_LIBS OFF" ) if(SWMMInterface_ADDED) target_include_directories( diff --git a/scripts/cmake/DependenciesExternalProject.cmake b/scripts/cmake/DependenciesExternalProject.cmake index ec14fe15943..5a9c922131b 100644 --- a/scripts/cmake/DependenciesExternalProject.cmake +++ b/scripts/cmake/DependenciesExternalProject.cmake @@ -202,7 +202,7 @@ if(OGS_USE_MPI) set(HDF5_PREFER_PARALLEL ON) list(APPEND _hdf5_options "-DHDF5_ENABLE_PARALLEL=ON") endif() -if(WIN32) +if(WIN32 OR HDF5_USE_STATIC_LIBRARIES) set(HDF5_USE_STATIC_LIBRARIES ON) list(APPEND _hdf5_options "-DBUILD_SHARED_LIBS=OFF") endif() diff --git a/scripts/cmake/MetisSetup.cmake b/scripts/cmake/MetisSetup.cmake index 6c6fde90cae..73131168b26 100644 --- a/scripts/cmake/MetisSetup.cmake +++ b/scripts/cmake/MetisSetup.cmake @@ -10,7 +10,10 @@ include(${GKLIB_PATH}/GKlibSystem.cmake) # Metis library file(GLOB _metis_sources ${metis_SOURCE_DIR}/libmetis/*.c) -ogs_add_library(ogs_metis ${GKlib_sources} ${_metis_sources}) +if(WIN32) + set(_metis_static STATIC) +endif() +ogs_add_library(ogs_metis ${_metis_static} ${GKlib_sources} ${_metis_sources}) target_compile_definitions(ogs_metis PUBLIC USE_GKREGEX) target_include_directories( ogs_metis PUBLIC ${metis_SOURCE_DIR}/GKlib ${metis_SOURCE_DIR}/include diff --git a/scripts/cmake/ProjectSetup.cmake b/scripts/cmake/ProjectSetup.cmake index 597971cecf2..16bd5894c4d 100644 --- a/scripts/cmake/ProjectSetup.cmake +++ b/scripts/cmake/ProjectSetup.cmake @@ -40,8 +40,12 @@ endif() file(RELATIVE_PATH relDir ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR} ) -list(APPEND CMAKE_INSTALL_RPATH ${BASEPOINT} ${BASEPOINT}/${relDir}) -list(APPEND CMAKE_BUILD_RPATH ${BASEPOINT} ${BASEPOINT}/${relDir}) +list(APPEND CMAKE_INSTALL_RPATH ${BASEPOINT} ${BASEPOINT}/${relDir} + ${BASEPOINT}/${CMAKE_INSTALL_LIBDIR} # Python modules +) +list(APPEND CMAKE_BUILD_RPATH ${BASEPOINT} ${BASEPOINT}/${relDir} + ${BASEPOINT}/${CMAKE_INSTALL_LIBDIR} # Python modules +) # Some external dependencies always use lib instead of lib64, Fix for # lib64-based systems, e.g. OpenSUSE: diff --git a/scripts/cmake/PythonSetup.cmake b/scripts/cmake/PythonSetup.cmake index 833bb5718ca..ef6be751483 100644 --- a/scripts/cmake/PythonSetup.cmake +++ b/scripts/cmake/PythonSetup.cmake @@ -1,6 +1,9 @@ # cmake-lint: disable=C0103 -set(_python_version_max "...<3.11") +if(OGS_USE_PYTHON) + set(_python_version_max "...<3.11") +endif() + if(OGS_USE_PIP) set(Python_ROOT_DIR ${PROJECT_BINARY_DIR}/.venv) set(CMAKE_REQUIRE_FIND_PACKAGE_Python TRUE) @@ -38,9 +41,15 @@ endif() set(_python_componets Interpreter) if(OGS_USE_PYTHON) + list(APPEND _python_componets Development.Embed) +endif() +if(OGS_BUILD_PYTHON_MODULE) + list(APPEND _python_componets Development.Module) +endif() +if(OGS_USE_PYTHON OR OGS_BUILD_PYTHON_MODULE) set(CMAKE_REQUIRE_FIND_PACKAGE_Python TRUE) - list(APPEND _python_componets Development) endif() + find_package( Python ${ogs.minimum_version.python}${_python_version_max} COMPONENTS ${_python_componets} diff --git a/scripts/cmake/packaging/Pack.cmake b/scripts/cmake/packaging/Pack.cmake index 8c1daaf61bc..33b91a33fc6 100644 --- a/scripts/cmake/packaging/Pack.cmake +++ b/scripts/cmake/packaging/Pack.cmake @@ -76,7 +76,9 @@ if(OGS_USE_PYTHON) install(FILES ${PYTHON_RUNTIME_LIBS} DESTINATION bin) file(COPY ${PYTHON_RUNTIME_LIBS} DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) else() - install(FILES ${Python_LIBRARIES} DESTINATION bin) + file(INSTALL ${Python_LIBRARIES} DESTINATION ${CMAKE_INSTALL_LIBDIR} + FOLLOW_SYMLINK_CHAIN + ) endif() endif() diff --git a/setup.py b/setup.py new file mode 100644 index 00000000000..177b162c830 --- /dev/null +++ b/setup.py @@ -0,0 +1,69 @@ +from skbuild import setup +from setuptools import find_packages + +import os +import platform +import re +import subprocess +import sys + + +def get_version(): + git_version = "" + if "OGS_VERSION" in os.environ: + git_version = os.environ["OGS_VERSION"] + else: + git_describe_cmd = ["git describe --tags"] + if platform.system() == "Windows": + git_describe_cmd = ["git", "describe", "--tags"] + git_version = subprocess.run( + git_describe_cmd, + capture_output=True, + text=True, + shell=True, + ).stdout.strip() + + if re.match("\d+\.\d+\.\d+-\d+-g\w+", git_version): + # Make it PEP 440 compliant + # e.g. 6.4.2-1140-g85bbc8b4e1 -> 6.4.2.dev1140 + m = re.match(".+?(?=-g[\w]*$)", git_version) # strip out commit hash + if m: + return m.group(0).replace("-", ".dev") # insert dev + print("ERROR: Could not get ogs version!") + exit(1) + + return git_version + + +sys.path.append(os.path.join("Applications", "Python")) +from ogs._internal.provide_ogs_cli_tools_via_wheel import binaries_list + +console_scripts = [] +for b in binaries_list: + console_scripts.append(f"{b}=ogs._internal.provide_ogs_cli_tools_via_wheel:{b}") + +cmake_preset = "wheel" +if platform.system() == "Windows": + cmake_preset += "-win" + +from pathlib import Path + +this_directory = Path(__file__).parent +long_description = (this_directory / "README.md").read_text() + +setup( + name="ogs", + version=get_version(), + description="OpenGeoSys Python Module", + long_description=long_description, + long_description_content_type="text/markdown", + author="OpenGeoSys Community", + license="BSD-3-Clause", + packages=find_packages(where="Applications/Python"), + package_dir={"": "Applications/Python"}, + cmake_install_dir="Applications/Python/ogs", + extras_require={"test": ["pytest"]}, + cmake_args=[f"--preset {cmake_preset}", "-B ."], + python_requires=">=3.7", + entry_points={"console_scripts": console_scripts}, +) diff --git a/web/content/docs/devguide/advanced/python-wheel/index.md b/web/content/docs/devguide/advanced/python-wheel/index.md new file mode 100644 index 00000000000..91154b43ab4 --- /dev/null +++ b/web/content/docs/devguide/advanced/python-wheel/index.md @@ -0,0 +1,75 @@ ++++ +date = "2022-02-09T11:00:13+01:00" +title = "Python wheel development" +author = "Lars Bilke" +weight = 1068 + +[menu] + [menu.devguide] + parent = "advanced" ++++ + +## Local setup + +Python wheel builds are driven by [scikit-build](https://scikit-build.readthedocs.io/en/latest/) which basically is a `setuptools`-wrapper for CMake-based projects. + +The entrypoint is `setup.py` in the root directory. It uses the `wheel` CMake preset (or `wheel-win` on Windows). + +You can locally develop and test with the following setup: + +```bash +# Create a virtual environment inside your source directory +python3 -m venv .venv +# Activate the environment +source .venv/bin/activate +# Install (build) the local Python project +pip install -v .[test] +... +Successfully installed ogs-6.4.2.dev1207 +``` + +The `pip install`-step starts a new CMake-based ogs build in `_skbuild`-subdirectory (inside the source code) using the `wheel`-preset. When the CMake build is done it installs the wheel into the activated virtual environment and you can interact with it, e.g.: + +```bash +# Run python tests +pytest +============================================== test session starts =============================================== +platform darwin -- Python 3.10.6, pytest-7.1.3, pluggy-1.0.0 +rootdir: ~/code/ogs/ogs, configfile: pyproject.toml, testpaths: Tests +collected 2 items + +Tests/Python/test_cli.py . [ 50%] +Tests/Python/test_simlator.py . [100%] + +=============================================== 2 passed in 0.55s ================================================ + +# Start the python interpreter +python3 +>>> import ogs.simulator as sim +>>> sim.initialize(["", "--help"]) +``` + +If you make modifications you need to run `pip install .[test]` again (or for temporary modifications you can directly edit inside the virtual environment, e.g. in `.venv/lib/python3.10/site-packages/ogs`). + +The contents of `_skbuild/[platform-specific]/cmake-install` will make up the wheel. + +## CI + +For generating the various wheels for different Python versions and platforms [`cibuildwheel`](https://cibuildwheel.readthedocs.io/en/stable/) is used. + +You can test it locally with, e.g. only building for Python 3.10: + +```bash +CIBW_BUILD="cp310*" pipx run cibuildwheel +``` + +Please note that on Linux `cibuildwheel` runs the builds inside [manylinux](https://github.com/pypa/manylinux) Docker containers. On other platforms the build happens with native tools. See the [cibuildwheel docs](https://cibuildwheel.readthedocs.io/en/stable/#how-it-works) for more information. + +Wheels are generated in the `wheelhouse/`-folder. + +`cibuildwheel` is configured in `pyproject.toml`: + +```toml +[tool.cibuildwheel] +... +``` diff --git a/web/content/docs/userguide/basics/introduction/index.md b/web/content/docs/userguide/basics/introduction/index.md index 208d6437359..d808b4c07ce 100644 --- a/web/content/docs/userguide/basics/introduction/index.md +++ b/web/content/docs/userguide/basics/introduction/index.md @@ -15,14 +15,60 @@ weight = 1 post = "Download, install and run an OGS benchmark in 5 minutes! No development setup required." +++ -## Download +## Installation -Download the latest release of OpenGeoSys from the [Releases](/releases)-page. Be sure to pick the correct file for your operating system. +
-## Installation +Download the latest release of OpenGeoSys from the [Releases](/releases)-page. Be sure to pick the correct file for your operating system. OGS itself is a simple executable file so you can put it anywhere you like. For convenience you may put into a location which is in your `PATH`-environment variable which allows you to start the executable without specifying its full file path. +
+ +### Alternative: Install via `pip` + +You can also install ogs via Python's [`pip`-tool](https://packaging.python.org/en/latest/tutorials/installing-packages/): + +```bat +pip install ogs +``` + +If you install into an activated [virtual environment](https://docs.python.org/3/library/venv.html) then ogs and its tools are automatically also in the `PATH`. Otherwise `pip` will print instructions which directory needs to be added to the `PATH`. + +
+ +
+ +
+ +Install via Python's [`pip`-tool](https://packaging.python.org/en/latest/tutorials/installing-packages/): + +```bash +pip install ogs +``` + +You may want to set up and activate a [virtual environment](https://docs.python.org/3/library/venv.html) before. + +You could also use [`pipx`](https://pypa.github.io/pipx/) to install into an isolated environment. + +
+ +
+ +See Linux tab! + +
+ +
+ +### Limitations of the `pip`-based installation + +- Serial config only! For PETSc-support please use a [Singularity container]({{< relref "container" >}}). +- No embedded Python interpreter, i.e. no Python boundary conditions! +- A Python (3.8 - 3.11) installation with `pip` is required. + +
+ ## Download benchmarks You can download the latest benchmark files from GitLab: diff --git a/web/data/versions.json b/web/data/versions.json index d064e9cdf8d..45f0d34dee4 100644 --- a/web/data/versions.json +++ b/web/data/versions.json @@ -71,8 +71,8 @@ } }, "cpm": { - "package_file_id": 119, - "package_file_sha256": "7d98b148e6d24acd72d17d2503f3ce3bab74029da5b3328788b2d4c379e9dcac" + "package_file_id": 182, + "package_file_sha256": "00d7dea24754ad415e7003535b36a7d5b4e7224701341f5ca587f93e42b63563" }, "ext": { "cache_hash": "e6f3f1f4c29c6c5f096f89785e6e245bdf39ac1a"