diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4cbec58..91386b7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -193,9 +193,10 @@ jobs: - name: Install hwloc shell: pwsh run: | + $Env:HWLOC_VERSION = $(cat ./dev/hwloc_version) git clone https://github.com/open-mpi/hwloc.git cd hwloc - git checkout 82d8d2c1ad17e0b48f046f997271e45946bd342e + git checkout $Env:HWLOC_VERSION cd contrib/windows-cmake mkdir build diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 69385d3..c55e34e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,15 +8,13 @@ version: 2 build: os: ubuntu-24.04 tools: - python: "3.12" + python: "miniconda-latest" + apt_packages: + - doxygen # Build documentation in the "docs/" directory with Sphinx sphinx: configuration: docs/source/conf.py -# Optionally, but recommended, -# declare the Python requirements required to build your documentation -# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -# python: -# install: -# - requirements: docs/requirements.txt +conda: + environment: dev/pyhwloc_dev.yml diff --git a/CMakeLists.txt b/CMakeLists.txt index 4655a9c..a507542 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ project(pyhwloc LANGUAGES C VERSION 0.0.0) list(APPEND CMAKE_MODULE_PATH "${pyhwloc_SOURCE_DIR}/cmake/") option(PYHWLOC_FETCH_HWLOC "Fetch hwloc from GitHub instead of using system version" OFF) +option(PYHWLOC_WITH_CUDA "Add CUDA-related components" ON) set(PYHWLOC_BUILD_HWLOC_CMAKE OFF) if(WIN32) @@ -23,10 +24,12 @@ if(PYHWLOC_FETCH_HWLOC) set(PYHWLOC_CONFIG_HWLOC OFF CACHE BOOL "") include(FetchContent) + file(READ ${pyhwloc_SOURCE_DIR}/dev/hwloc_version HWLOC_VERSION) + message(STATUS "HWLOC_VERSION: ${HWLOC_VERSION}") FetchContent_Declare( hwloc GIT_REPOSITORY https://github.com/open-mpi/hwloc.git - GIT_TAG 82d8d2c1ad17e0b48f046f997271e45946bd342e + GIT_TAG ${HWLOC_VERSION} ) # Configure hwloc build options @@ -41,7 +44,7 @@ if(PYHWLOC_FETCH_HWLOC) -G ${CMAKE_GENERATOR} -DCMAKE_INSTALL_PREFIX=${PYHWLOC_OUTPUT_DIR} -DBUILD_SHARED_LIBS=ON - -DHWLOC_WITH_CUDA=ON + -DHWLOC_WITH_CUDA=${PYHWLOC_WITH_CUDA} -DHWLOC_ENABLE_PLUGINS=ON WORKING_DIRECTORY ${hwloc_SOURCE_DIR} ) @@ -93,26 +96,29 @@ else() endif() message(STATUS "HWLOC_LIBRARY: ${HWLOC_LIBRARY}") -find_package(CUDAToolkit REQUIRED COMPONENTS nvml cuda_driver cudart_static) target_link_libraries(pyhwloc PRIVATE ${HWLOC_LIBRARY}) target_include_directories(pyhwloc PRIVATE ${HWLOC_INCLUDE_DIR}) list(APPEND PYHWLOC_LIBS pyhwloc) -add_library(pyhwloc_cuda SHARED src/ext/cudr.c) -target_link_libraries(pyhwloc_cuda PRIVATE CUDA::cuda_driver ${HWLOC_LIBRARY}) -target_include_directories(pyhwloc_cuda PRIVATE ${HWLOC_INCLUDE_DIR}) -list(APPEND PYHWLOC_LIBS pyhwloc_cuda) +if(PYHWLOC_WITH_CUDA) + find_package(CUDAToolkit REQUIRED COMPONENTS nvml cuda_driver cudart_static) -add_library(pyhwloc_cudart SHARED src/ext/cudart.c) -target_link_libraries(pyhwloc_cudart PRIVATE CUDA::cudart ${HWLOC_LIBRARY}) -target_include_directories(pyhwloc_cudart PRIVATE ${HWLOC_INCLUDE_DIR}) -list(APPEND PYHWLOC_LIBS pyhwloc_cudart) + add_library(pyhwloc_cuda SHARED src/ext/cudr.c) + target_link_libraries(pyhwloc_cuda PRIVATE CUDA::cuda_driver ${HWLOC_LIBRARY}) + target_include_directories(pyhwloc_cuda PRIVATE ${HWLOC_INCLUDE_DIR}) + list(APPEND PYHWLOC_LIBS pyhwloc_cuda) -add_library(pyhwloc_nvml SHARED src/ext/nvml.c) -target_link_libraries(pyhwloc_nvml PRIVATE CUDA::nvml ${HWLOC_LIBRARY}) -target_include_directories(pyhwloc_nvml PRIVATE ${HWLOC_INCLUDE_DIR}) -list(APPEND PYHWLOC_LIBS pyhwloc_nvml) + add_library(pyhwloc_cudart SHARED src/ext/cudart.c) + target_link_libraries(pyhwloc_cudart PRIVATE CUDA::cudart ${HWLOC_LIBRARY}) + target_include_directories(pyhwloc_cudart PRIVATE ${HWLOC_INCLUDE_DIR}) + list(APPEND PYHWLOC_LIBS pyhwloc_cudart) + + add_library(pyhwloc_nvml SHARED src/ext/nvml.c) + target_link_libraries(pyhwloc_nvml PRIVATE CUDA::nvml ${HWLOC_LIBRARY}) + target_include_directories(pyhwloc_nvml PRIVATE ${HWLOC_INCLUDE_DIR}) + list(APPEND PYHWLOC_LIBS pyhwloc_nvml) +endif() include(GenerateExportHeader) diff --git a/README.rst b/README.rst index f044dbc..3d0369b 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,5 @@ Python Interface for the Portable Hardware Locality (hwloc) Library =================================================================== -- `Official site `__ of the hwloc library. \ No newline at end of file +- `Document `__. +- `Official site `__ of the hwloc library. diff --git a/dev/Dockerfile.cpu b/dev/Dockerfile.cpu index 1a119a7..28e39d2 100644 --- a/dev/Dockerfile.cpu +++ b/dev/Dockerfile.cpu @@ -18,10 +18,12 @@ RUN \ git clone https://github.com/doxygen/doxygen.git && \ cd doxygen && git checkout d49be63057d71f487312f43339516bc92cc685fa && cd - +COPY hwloc_version ws/hwloc_version + RUN \ cd /ws/ && \ git clone https://github.com/open-mpi/hwloc.git && \ - cd hwloc && git checkout 82d8d2c1ad17e0b48f046f997271e45946bd342e && cd - + cd hwloc && git checkout $(cat /ws/hwloc_version) && cd - SHELL ["mamba", "run", "--no-capture-output", "-n", "base", "/bin/bash", "-c"] diff --git a/dev/hwloc_version b/dev/hwloc_version new file mode 100644 index 0000000..991a53d --- /dev/null +++ b/dev/hwloc_version @@ -0,0 +1 @@ +82d8d2c1ad17e0b48f046f997271e45946bd342e \ No newline at end of file diff --git a/docs/source/build.rst b/docs/source/build.rst index ca61dd8..2ec0e63 100644 --- a/docs/source/build.rst +++ b/docs/source/build.rst @@ -99,8 +99,11 @@ A complete list of options available for the ``--config-settings=``: - ``build-dir=/path/to/build/dir`` for specifying a build dir. - ``hwloc-src-dir=/path/to/hwloc-src`` for using a local checkout of hwloc. This assumes the src directory is the git repo, which is not the same as the release tarball. -- ``hwloc-root-dir=/path/to/hwloc`` to specify the path of an existing hwloc installation. +- ``hwloc-root-dir=/path/to/hwloc`` to specify the path of an existing binary hwloc + installation. - ``fetch-hwloc=True`` to build the fat wheel. +- ``with-cuda=False`` to build pyhwloc without CUDA plugins. This is used for building + document in a minimum environment and is not tested for other purposes. The binary wheel uses plugins by default. Due to the plugins support, all symbols from hwloc are loaded into the linker's public name space using diff --git a/docs/source/conf.py b/docs/source/conf.py index b8f9a23..ffd6013 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -9,6 +9,11 @@ from __future__ import annotations import os +import subprocess +import sys +import tempfile +from contextlib import contextmanager +from typing import Iterator project = "pyhwloc" copyright = "2025, Jiaming Yuan" @@ -36,21 +41,6 @@ intersphinx_mapping = {"python": ("https://docs.python.org/3.10", None)} -# -- Breathe -breathe_default_project = "pyhwloc" -breathe_domain_by_extension = {"h": "c"} - -CURR_PATH = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) # source -PROJECT_ROOT = os.path.normpath(os.path.join(CURR_PATH, os.path.pardir, os.path.pardir)) - -hwloc_xml_path = os.environ.get("PYHWLOC_XML_PATH", None) -if hwloc_xml_path is None: - hwloc_xml_path = os.path.join( - PROJECT_ROOT, os.path.pardir, "hwloc/doc/doxygen-doc/xml" - ) -breathe_projects = {"pyhwloc": hwloc_xml_path} -print("beathe projects", breathe_projects) - # -- Build environment os.environ["PYHWLOC_SPHINX"] = "1" @@ -62,3 +52,103 @@ # path to where to save gallery generated output "gallery_dirs": ["examples"], } + + +def is_readthedocs_build() -> bool: + if os.environ.get("READTHEDOCS", None) == "True": + return True + return False + + +def normpath(path: str) -> str: + return os.path.normpath(os.path.abspath(path)) + + +@contextmanager +def _chdir(dirname: str) -> Iterator[None]: + pwd = normpath(os.path.curdir) + try: + os.chdir(dirname) + yield + finally: + os.chdir(pwd) + + +def build_pyhwloc_xml() -> str: + """Build and install pyhwloc, returns the path to the doxygen xml files.""" + if sys.platform == "win32": + raise NotImplementedError("Read the docs environment should be Linux.") + + hwloc_version_path = os.path.join( + PROJECT_ROOT, + "dev", + "hwloc_version", + ) + with open(hwloc_version_path, "r") as fd: + hwloc_version = fd.read().strip() + + with tempfile.TemporaryDirectory() as tmpdir: + pwd = normpath(os.path.curdir) + script = f"""#!/usr/bin/env bash +# Clone +git clone https://github.com/open-mpi/hwloc.git +cd hwloc +git checkout {hwloc_version} + +# Config +./autogen.sh +./configure --disable-nvml --enable-doxygen + +# Build doc +cd doc +HWLOC_DOXYGEN_GENERATE_XML=YES doxygen ./doxygen.cfg +# Result is in `hwloc/doc/doxygen-doc/xml` + +# Copy and cleanup +cp -r doxygen-doc/xml {pwd}/ +cd .. +git clean -xdf +""" + script_path = os.path.join(tmpdir, "build_xml.sh") + with open(script_path, "w") as fd: + fd.write(script) + + with _chdir(tmpdir): + subprocess.check_call(["bash", script_path]) + xml_path = os.path.join(pwd, "xml") + + # Install pyhwloc while we have the hwloc source + hwloc_src_dir = os.path.join(tmpdir, "hwloc") + subprocess.check_call( + [ + "pip", + "install", + PROJECT_ROOT, + "--config-settings=fetch-hwloc=True", + f"--config-settings=hwloc-src-dir={hwloc_src_dir}", + "--config-settings=with-cuda=False", + "--no-deps", + "--no-build-isolation", + ] + ) + + return xml_path + + +# -- Breathe +breathe_default_project = "pyhwloc" +breathe_domain_by_extension = {"h": "c"} + +CURR_PATH = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) # source +PROJECT_ROOT = os.path.normpath(os.path.join(CURR_PATH, os.path.pardir, os.path.pardir)) + +if is_readthedocs_build(): + hwloc_xml_path: str | None = build_pyhwloc_xml() +else: + hwloc_xml_path = os.environ.get("PYHWLOC_XML_PATH", None) + if hwloc_xml_path is None: + hwloc_xml_path = os.path.join( + PROJECT_ROOT, os.path.pardir, "hwloc/doc/doxygen-doc/xml" + ) +breathe_projects = {"pyhwloc": hwloc_xml_path} +print("breathe projects", breathe_projects) diff --git a/docs/source/dev.rst b/docs/source/dev.rst index 231a4f9..ad79180 100644 --- a/docs/source/dev.rst +++ b/docs/source/dev.rst @@ -8,6 +8,11 @@ Symbol Conflicts The hwloc is loaded into the public linker name space to support hwloc plugins. This might have unintended consequences. +Update hwloc +============ + +Update the commit hash in ``dev/hwloc_version``. + Design Decisions ================ diff --git a/hatch/backend.py b/hatch/backend.py index 2248cf4..aae7d10 100644 --- a/hatch/backend.py +++ b/hatch/backend.py @@ -10,7 +10,7 @@ import hatchling.build -from .hook import BUILD_KEY, FETCH_KEY, ROOT_KEY, SRC_KEY +from .hook import BUILD_KEY, CUDA_KEY, FETCH_KEY, ROOT_KEY, SRC_KEY @contextmanager @@ -26,6 +26,10 @@ def build_config(config_settings: dict[str, Any] | None) -> Iterator[None]: os.environ[SRC_KEY] = config_settings["hwloc-src-dir"] if "hwloc-root-dir" in config_settings: os.environ[ROOT_KEY] = config_settings["hwloc-root-dir"] + if "with-cuda" in config_settings: + v = config_settings["with-cuda"] + assert v in ("True", "False") + os.environ[CUDA_KEY] = v try: yield finally: @@ -37,6 +41,8 @@ def build_config(config_settings: dict[str, Any] | None) -> Iterator[None]: del os.environ[SRC_KEY] if ROOT_KEY in os.environ: del os.environ[ROOT_KEY] + if CUDA_KEY in os.environ: + del os.environ[CUDA_KEY] def build_wheel( diff --git a/hatch/hook.py b/hatch/hook.py index f3baed6..1488244 100644 --- a/hatch/hook.py +++ b/hatch/hook.py @@ -16,6 +16,7 @@ from packaging.tags import platform_tags FETCH_KEY = "PYHWLOC_FETCH_HWLOC" +CUDA_KEY = "PYHWLOC_WITH_CUDA" BUILD_KEY = "PYHWLOC_BUILD_DIR" ROOT_KEY = "PYHWLOC_HWLOC_ROOT_DIR" SRC_KEY = "PYHWLOC_HWLOC_SRC_DIR" @@ -123,8 +124,6 @@ def run_cmake_build( str(build_path), "--config", build_type, - "--parallel", - str(parallel_jobs), ] print(f"CMake install: {' '.join(install_cmd)}") result = subprocess.run(install_cmd, check=False) @@ -168,6 +167,11 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None: raise FileNotFoundError(root_dir) cmake_args.append(f"-DHWLOC_ROOT={root_dir}") + # No CUDA in RTD + with_cuda = os.environ.get(CUDA_KEY, None) + if with_cuda == "False": + cmake_args.append(f"-D{CUDA_KEY}=OFF") + # Source path if os.environ.get(SRC_KEY, None) is not None: src_dir = os.environ[SRC_KEY] diff --git a/pyproject.toml b/pyproject.toml index 298daa6..8a41db7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ classifiers=[ [project.urls] source = "https://github.com/open-mpi/pyhwloc" +documentation = "https://pyhwloc.readthedocs.io/" [tool.hatch.version] path = "src/pyhwloc/hwloc/__init__.py"