-
Notifications
You must be signed in to change notification settings - Fork 34
Adds axom module to python interface with axom.sidre #1896
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
98938ac
bc208f0
9317cc2
ba142d6
aa6f6da
c4196e0
83292d0
6c1ffa6
158da35
8c69385
073407d
1fecaf3
52ef44f
71e8428
6264f0b
61a0792
46c633d
c18ab0b
691471b
23c93eb
77f6f49
71fd96a
279e34b
16c6a1a
9d4b138
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| import os | ||
| import shutil | ||
| import socket | ||
| import tempfile | ||
| from os.path import join as pjoin | ||
|
|
||
| from spack_repo.builtin.build_systems.cached_cmake import ( | ||
|
|
@@ -277,15 +278,20 @@ class Axom(CachedCMakePackage, CudaPackage, ROCmPackage): | |
| depends_on("mfem~mpi", when="~mpi") | ||
| depends_on("mfem@4.5.0:", when="@0.7.0:") | ||
|
|
||
| depends_on("python", when="+python") | ||
|
|
||
| # Python | ||
| with when("+python"): | ||
| depends_on("python") | ||
|
|
||
| # extending python allows spack environment views to import axom from python | ||
| extends("python") | ||
|
|
||
| depends_on("py-nanobind@2.7.0:") | ||
| depends_on("py-pytest") | ||
| depends_on("py-packaging") | ||
| depends_on("py-pygments") | ||
| depends_on("py-numpy") | ||
| depends_on("py-mpi4py", when="+mpi") | ||
| depends_on("conduit+python") | ||
| depends_on("conduit+python", when="+conduit") | ||
|
|
||
| # Devtools | ||
| with when("+devtools"): | ||
|
|
@@ -757,6 +763,16 @@ def initconfig_package_entries(self): | |
| python_bin_dir = get_spec_path(spec, "python", path_replacements, use_bin=True) | ||
| entries.append(cmake_cache_path("Python_EXECUTABLE", pjoin(python_bin_dir, "python3"))) | ||
|
|
||
| if spec.satisfies("+python"): | ||
| # Install Axom's Python package(s) so a spack environment view merges them into | ||
| # a single site-packages and `import axom.sidre` works without updating PYTHONPATH | ||
| entries.append( | ||
| cmake_cache_path( | ||
| "AXOM_PYTHON_MODULE_INSTALL_PREFIX", | ||
| spec["python"].package.platlib, | ||
| ) | ||
| ) | ||
|
Comment on lines
+766
to
+774
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this PR require updating the host-configs?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call -- It won't cause our CI to fail since it's related to how others import
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I created an issue for this: #1900 |
||
|
|
||
| if spec.satisfies("^py-jsonschema"): | ||
| jsonschema_dir = get_spec_path(spec, "py-jsonschema", path_replacements, use_bin=True) | ||
| jsonschema_path = os.path.join(jsonschema_dir, "jsonschema") | ||
|
|
@@ -787,20 +803,23 @@ def initconfig_package_entries(self): | |
| ) | ||
|
|
||
| if spec.satisfies("+python"): | ||
| python_platlib = spec["python"].package.platlib | ||
|
|
||
| # pytest requires pluggy and iniconfig | ||
| # newer pytest releases also import packaging/pygments from separate Spack prefixes. | ||
| for dep in ( | ||
| "py-nanobind", | ||
| "py-pytest", | ||
| "py-numpy", | ||
| "py-pluggy", | ||
| "py-iniconfig", | ||
| "py-packaging", | ||
| "py-pygments", | ||
| "py-mpi4py", | ||
| ): | ||
| if spec.satisfies("^{0}".format(dep)): | ||
| dep_dir = get_spec_path(spec, dep, path_replacements, use_lib=True) | ||
| py_libdir = join_path( | ||
| dep_dir, f"python{spec['python'].version.up_to(2)}", "site-packages" | ||
| ) | ||
| dep_dir = get_spec_path(spec, dep, path_replacements) | ||
| py_libdir = join_path(dep_dir, python_platlib) | ||
| entries.append( | ||
| cmake_cache_path("%s_DIR" % dep.upper().replace("-", "_"), py_libdir) | ||
| ) | ||
|
|
@@ -845,7 +864,8 @@ def build_test(self): | |
| def test_install_using_cmake(self): | ||
| """build example with cmake and run""" | ||
| example_src_dir = join_path(self.prefix.examples.axom, "using-with-cmake") | ||
| example_stage_dir = "./cmake" | ||
| example_test_dir = tempfile.mkdtemp(prefix="axom-cmake-example-") | ||
| example_stage_dir = join_path(example_test_dir, "using-with-cmake") | ||
| shutil.copytree(example_src_dir, example_stage_dir) | ||
| with working_dir(join_path(example_stage_dir, "build"), create=True): | ||
| cmake_args = ["-C ../host-config.cmake", example_src_dir] | ||
|
|
@@ -861,10 +881,60 @@ def test_install_using_cmake(self): | |
| def test_install_using_make(self): | ||
| """build example with make and run""" | ||
| example_src_dir = join_path(self.prefix.examples.axom, "using-with-make") | ||
| example_stage_dir = "./make" | ||
| example_test_dir = tempfile.mkdtemp(prefix="axom-make-example-") | ||
| example_stage_dir = join_path(example_test_dir, "using-with-make") | ||
| shutil.copytree(example_src_dir, example_stage_dir) | ||
| with working_dir(example_stage_dir, create=True): | ||
| make(f"AXOM_DIR={self.prefix}") | ||
| example = Executable("./example") | ||
| example() | ||
| make("clean") | ||
|
|
||
| @run_after("install", when="+examples+python+tools components=sidre") | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tested this with: Here's the relevant part of the installation test: |
||
| @on_package_attributes(run_tests=True) | ||
| def test_install_using_python(self): | ||
| """run python example against installed axom""" | ||
| example = join_path(self.prefix.examples.axom, "using-with-python", "example.py") | ||
| python_runner = join_path(self.prefix.bin, "run_python_with_axom.sh") | ||
| if not os.path.isfile(example): | ||
| raise RuntimeError("Missing installed python example: {0}".format(example)) | ||
| if not os.path.isfile(python_runner): | ||
| raise RuntimeError("Missing installed python runner: {0}".format(python_runner)) | ||
| run_python = Executable(python_runner) | ||
| run_python(example) | ||
|
|
||
| @run_after("install", when="+python components=sidre") | ||
| @on_package_attributes(run_tests=True) | ||
| def test_axom_sidre_installed_into_site_packages(self): | ||
| """Check axom.sidre installed into a site-packages-shaped prefix | ||
| and imports from view-shaped site-packages paths. | ||
| """ | ||
| python_pkg = self.spec["python"].package | ||
| python_platlib = python_pkg.platlib | ||
| site_packages = join_path(self.prefix, python_platlib) | ||
| sidre_pkg_dir = join_path(site_packages, "axom", "sidre") | ||
| if not os.path.isdir(sidre_pkg_dir): | ||
| raise RuntimeError( | ||
| "axom.sidre was not installed under the interpreter platlib: " | ||
| "{0}".format(sidre_pkg_dir) | ||
| ) | ||
|
|
||
| # Assemble the Python package directories a view would merge into site-packages. | ||
| import_path = [site_packages] | ||
| if self.spec.satisfies("+conduit"): | ||
| for conduit_py in ( | ||
| join_path(self.spec["conduit"].prefix, python_platlib), | ||
| join_path(self.spec["conduit"].prefix, "python-modules"), | ||
| ): | ||
| if os.path.isdir(conduit_py): | ||
| import_path.append(conduit_py) | ||
|
|
||
| for dep in ("py-numpy", "py-mpi4py"): | ||
| if self.spec.satisfies("^{0}".format(dep)): | ||
| dep_py = join_path(self.spec[dep].prefix, python_platlib) | ||
| if os.path.isdir(dep_py): | ||
| import_path.append(dep_py) | ||
|
|
||
| imports = "import axom.sidre as s; import numpy; print('axom.sidre', s.__version__)" | ||
| python = Executable(join_path(self.spec["python"].prefix.bin, "python3")) | ||
| python("-c", imports, extra_env={"PYTHONPATH": ":".join(import_path)}) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -138,49 +138,118 @@ endif() | |
|
|
||
|
|
||
| if(NANOBIND_FOUND) | ||
| nanobind_add_module(pysidre nanobind_sidre.cpp) | ||
| # Python bindings for Sidre. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think most of this logic belongs in the parent directory's On the other hand, I'm not against refactoring it in this PR.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leave it for now and refactor when we have another Python module to avoid churn? |
||
| # The pure-Python package scaffolding lives once under src/python/src/ | ||
|
|
||
| # site-packages-shaped install directory for Axom's Python package(s). | ||
| # Keep this relative to the install prefix so `cmake --install --prefix` | ||
| # relocates the Python package along with Axom's other install artifacts. | ||
| set(_axom_python_install_default | ||
| "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") | ||
| set(_axom_python_install_description | ||
| "Install destination for Axom's Python package(s), relative to the install prefix") | ||
| set(AXOM_PYTHON_MODULE_INSTALL_PREFIX | ||
| "${_axom_python_install_default}" | ||
| CACHE PATH | ||
| "${_axom_python_install_description}") | ||
|
|
||
| if(IS_ABSOLUTE "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}") | ||
| file(RELATIVE_PATH _axom_python_install_relpath | ||
| "${CMAKE_INSTALL_PREFIX}" | ||
| "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}") | ||
| if(NOT _axom_python_install_relpath MATCHES "^\\.\\.") | ||
| set(AXOM_PYTHON_MODULE_INSTALL_PREFIX | ||
| "${_axom_python_install_relpath}" | ||
| CACHE PATH | ||
| "${_axom_python_install_description}" | ||
| FORCE) | ||
| endif() | ||
| unset(_axom_python_install_relpath) | ||
| endif() | ||
| unset(_axom_python_install_default) | ||
| unset(_axom_python_install_description) | ||
|
|
||
| # Root of the staged package tree in the build directory. | ||
| # The build-tree interpreter (and run_python_with_axom.sh, via _PYEXT_DIR) | ||
| # puts this single directory on PYTHONPATH and then 'import axom.sidre' works. | ||
| set(_axom_py_build_root "${PROJECT_BINARY_DIR}/python") | ||
| set(_axom_py_pkg_src "${CMAKE_CURRENT_SOURCE_DIR}/../../python/src") | ||
|
|
||
| # Build the extension under the shared 'axom' nanobind domain. | ||
| # All of Axom's extension modules (currently just axom.sidre) share NB_DOMAIN=axom | ||
| # so that C++ types bound in one module, e.g. a sidre::Group*, | ||
| # recognized when passed to another module built from the same Axom build. | ||
| # nanobind only shares type bindings across modules that agree on | ||
| # domain *and* nanobind ABI, compiler, and build mode. | ||
| nanobind_add_module(_sidre nanobind_sidre.cpp NB_DOMAIN axom) | ||
| # conduit::conduit_python provides conduit_python.hpp | ||
| # and is needed only by the binding translation unit, not by libsidre | ||
| target_link_libraries(pysidre PRIVATE sidre conduit::conduit_python) | ||
| target_link_libraries(_sidre PRIVATE sidre conduit::conduit_python) | ||
|
|
||
| # Place the built extension directly into the staged package tree so the | ||
| # build tree is import-ready without an extra copy step. | ||
| set_target_properties(_sidre PROPERTIES | ||
| LIBRARY_OUTPUT_DIRECTORY "${_axom_py_build_root}/axom/sidre") | ||
|
|
||
| # Use HIP executable linker flags for the python module | ||
| # (CMake treats modules separately from executables, | ||
| # executable flags not automatically applied) | ||
| if(AXOM_ENABLE_HIP) | ||
| # Make CMake compile the pysidre file with the HIP compiler. | ||
| # Make CMake compile the binding file with the HIP compiler. | ||
| set_source_files_properties(nanobind_sidre.cpp PROPERTIES LANGUAGE HIP) | ||
|
|
||
| string (REPLACE " " ";" MODULE_LINK_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") | ||
| target_link_options(pysidre PRIVATE ${MODULE_LINK_FLAGS}) | ||
| target_link_options(_sidre PRIVATE ${MODULE_LINK_FLAGS}) | ||
| endif() | ||
|
|
||
| install(TARGETS pysidre LIBRARY DESTINATION lib) | ||
| # Stage the pure-Python package scaffolding into the build tree at configure | ||
| # time (axom/ namespace root + py.typed, axom/sidre/ re-export). | ||
| axom_configure_file("${_axom_py_pkg_src}/axom/__init__.py" | ||
| "${_axom_py_build_root}/axom/__init__.py" COPYONLY) | ||
| axom_configure_file("${_axom_py_pkg_src}/axom/py.typed" | ||
| "${_axom_py_build_root}/axom/py.typed" COPYONLY) | ||
| axom_configure_file("${_axom_py_pkg_src}/axom/sidre/__init__.py" | ||
| "${_axom_py_build_root}/axom/sidre/__init__.py" COPYONLY) | ||
| # Hand-written package stub: re-exports the generated _sidre.pyi statically | ||
| # so type checkers can see axom.sidre's surface | ||
| axom_configure_file("${_axom_py_pkg_src}/axom/sidre/__init__.pyi" | ||
| "${_axom_py_build_root}/axom/sidre/__init__.pyi" COPYONLY) | ||
|
|
||
| # Type stubs (PEP 561). nanobind_add_stub imports the module to introspect it, | ||
| # so its runtime dependencies (conduit for Node interop, numpy for ndarray returns) | ||
| # must be importable during the build. We seed PYTHON_PATH with the module's | ||
| # output directory plus the conduit/numpy install dirs from their cache variables when set; | ||
| # on an interpreter that already has conduit and numpy on its path these extra entries are harmless. | ||
| set(_pysidre_stub_pythonpath $<TARGET_FILE_DIR:pysidre>) | ||
| # Type stubs (PEP 561). nanobind_add_stub imports the module by its bare name | ||
| # ('import _sidre'), so the directory holding the built extension must be on | ||
| # PYTHON_PATH, along with the module's runtime deps (conduit for Node interop, | ||
| # numpy for ndarray returns). On an interpreter that already has conduit/numpy | ||
| # these extra entries are harmless. | ||
| set(_sidre_stub_pythonpath "${_axom_py_build_root}/axom/sidre") | ||
| if(CONDUIT_PYTHON_MODULE_DIR) | ||
| list(APPEND _pysidre_stub_pythonpath ${CONDUIT_PYTHON_MODULE_DIR}) | ||
| list(APPEND _sidre_stub_pythonpath ${CONDUIT_PYTHON_MODULE_DIR}) | ||
| endif() | ||
| if(PY_NUMPY_DIR) | ||
| list(APPEND _pysidre_stub_pythonpath ${PY_NUMPY_DIR}) | ||
| list(APPEND _sidre_stub_pythonpath ${PY_NUMPY_DIR}) | ||
| endif() | ||
|
|
||
| nanobind_add_stub( | ||
| pysidre_stub | ||
| MODULE pysidre | ||
| OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/pysidre.pyi" | ||
| MARKER_FILE "${CMAKE_CURRENT_BINARY_DIR}/py.typed" | ||
| PYTHON_PATH ${_pysidre_stub_pythonpath} | ||
| DEPENDS pysidre) | ||
|
|
||
| # Install the stub and py.typed marker next to the extension module so type checkers (mypy, pyright) can find them | ||
| install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pysidre.pyi" | ||
| "${CMAKE_CURRENT_BINARY_DIR}/py.typed" | ||
| DESTINATION lib) | ||
| _sidre_stub | ||
| MODULE _sidre | ||
| OUTPUT "${_axom_py_build_root}/axom/sidre/_sidre.pyi" | ||
| PYTHON_PATH ${_sidre_stub_pythonpath} | ||
| DEPENDS _sidre) | ||
|
|
||
| #-------------------------------------------------------------------------- | ||
| # Install the package tree into the site-packages-shaped prefix. | ||
| #-------------------------------------------------------------------------- | ||
| install(TARGETS _sidre | ||
| LIBRARY DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom/sidre") | ||
|
|
||
| install(FILES "${_axom_py_build_root}/axom/sidre/_sidre.pyi" | ||
| "${_axom_py_pkg_src}/axom/sidre/__init__.py" | ||
| "${_axom_py_pkg_src}/axom/sidre/__init__.pyi" | ||
| DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom/sidre") | ||
|
|
||
| # Namespace-root package files install once (not per component). | ||
| install(FILES "${_axom_py_pkg_src}/axom/__init__.py" | ||
| "${_axom_py_pkg_src}/axom/py.typed" | ||
| DESTINATION "${AXOM_PYTHON_MODULE_INSTALL_PREFIX}/axom") | ||
| endif() | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,3 +99,4 @@ needs and use cases. | |
| parallel_io_concepts | ||
| sidre_conduit | ||
| mfem_sidre_datacollection | ||
| python_interface | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
Uh oh!
There was an error while loading. Please reload this page.