From 76035e1c8444f3c41aa95fc1b5c711665b32148b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:37:39 +0000 Subject: [PATCH 01/12] Initial plan From 50bfd08e1400f306c4bddb7740914a3156a6ddaf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:56:38 +0000 Subject: [PATCH 02/12] feat: integrate papiex compilation into pip install via setup.py + compile_papiex.sh Co-authored-by: ilaflott <6273252+ilaflott@users.noreply.github.com> --- .github/workflows/build_and_test_epmt.yml | 24 +++++ .gitignore | 9 ++ Makefile | 31 ++++++- epmtdocs/docs/INSTALL.md | 61 ++++++++++++ src/MANIFEST.in | 1 + src/compile_papiex.sh | 108 ++++++++++++++++++++++ src/setup.py | 47 ++++++++++ 7 files changed, 280 insertions(+), 1 deletion(-) create mode 100755 src/compile_papiex.sh create mode 100644 src/setup.py diff --git a/.github/workflows/build_and_test_epmt.yml b/.github/workflows/build_and_test_epmt.yml index 39d2fd685..a0d831e08 100644 --- a/.github/workflows/build_and_test_epmt.yml +++ b/.github/workflows/build_and_test_epmt.yml @@ -155,6 +155,30 @@ jobs: run: | make OUTSIDE_DOCKER='YUP' papiex-dist + # ── Test pip install ./src with papiex compilation via compile_papiex.sh ── + # This exercises the new setup.py hook: compile_papiex.sh downloads + # papiex source from GitHub, compiles it, and installs .so files into + # src/epmt/lib/ so they are bundled into the pip package. + # gcc, make, and curl are already available from the earlier yum install step. + # compile_papiex.sh always exits 0 on graceful skips (missing tools, network + # errors), and setup.py catches non-zero exits and warns without failing the + # install — so pip install ./src should always succeed. + - name: Test pip install ./src (papiex compiled via compile_papiex.sh) + run: | + # Remove any pre-copied .so files to exercise the compilation path + rm -rf src/epmt/lib/ + # Install directly from source; setup.py invokes compile_papiex.sh + python3 -m pip install ./src + SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])") + if ls ${SITE_PACKAGES}/epmt/lib/*.so* 2>/dev/null | head -1 | grep -q .; then + echo "papiex shared libraries found — compile_papiex.sh succeeded" + else + echo "WARNING: papiex libraries not found after pip install ./src" + echo " (compile_papiex.sh may have skipped or failed gracefully)" + fi + # Uninstall before the sdist-based install below + python3 -m pip uninstall -y epmt || true + - name: make epmt pip-packaging run: | make dist python-dist dist-test diff --git a/.gitignore b/.gitignore index 6e93cd206..1596e3104 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,15 @@ src/epmt.egg-info/* src/papiex-epmt-install/* src/epmt/settings.py +# papiex compile_papiex.sh / pip install artifacts # +##################################################### +# vendored papiex C source (populated by 'make vendor-papiex') +src/vendor/ +# compiled papiex shared libraries (populated by compile_papiex.sh or make python-dist) +src/epmt/lib/ +# pip build log from setup.py +install_papiex.log + # virtualenv venv* diff --git a/Makefile b/Makefile index 6d49e2965..357da2234 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ PWD=$(shell pwd) install-py3-conda install-py3-pyenv install-deps \\ dist python-dist dist-test docker-dist docker-dist-test \\ epmt-dash \\ - papiex-dist \\ + papiex-dist vendor-papiex pip-install \\ epmt-full-release check-release \\ release \\ clean-extra clean-all clean distclean dashclean dockerclean papiexclean \\ @@ -305,6 +305,33 @@ $(PAPIEX_SRC_TARBALL): @echo "(PAPIEX_SRC_TARBALL) whoami: $(shell whoami)" curl -L --fail --retry 3 --retry-delay 5 -O $(PAPIEX_SRC_URL) ; \ ls $(PAPIEX_SRC_TARBALL) + +# Download papiex source into src/vendor/papiex/ so compile_papiex.sh +# can find it without needing network access at pip-install time. +vendor-papiex: + @echo "(vendor-papiex) whoami: $(shell whoami)" + @echo " ------ VENDOR PAPIEX SOURCE INTO src/vendor/papiex ------- " + @echo "PAPIEX_SRC_URL = ${PAPIEX_SRC_URL}" + if [ -d "src/vendor/papiex" ] && [ -f "src/vendor/papiex/Makefile" ]; then \ + echo "src/vendor/papiex already exists, skipping download." ; \ + else \ + mkdir -p src/vendor ; \ + curl -L --fail --retry 3 --retry-delay 5 \ + -o /tmp/papiex-vendor-$(PAPIEX_SRC_BRANCH).tar.gz $(PAPIEX_SRC_URL) ; \ + tar zxf /tmp/papiex-vendor-$(PAPIEX_SRC_BRANCH).tar.gz -C /tmp ; \ + TOP_DIR=$$(tar ztf /tmp/papiex-vendor-$(PAPIEX_SRC_BRANCH).tar.gz | head -1 | cut -d/ -f1) ; \ + mv /tmp/$${TOP_DIR} src/vendor/papiex ; \ + rm -f /tmp/papiex-vendor-$(PAPIEX_SRC_BRANCH).tar.gz ; \ + echo "papiex source vendored at src/vendor/papiex" ; \ + ls src/vendor/papiex ; \ + fi + +# Install epmt directly from src/ using pip, triggering compile_papiex.sh. +# Run 'make vendor-papiex' first to avoid a network download at install time. +pip-install: vendor-papiex + @echo "(pip-install) whoami: $(shell whoami)" + @echo " ------ PIP INSTALL epmt with PAPIEX COMPILATION ------- " + pip3 install ./src # ----------- \end PAPIEX THINGS ---------- # @@ -453,6 +480,8 @@ papiexclean: @echo "(papiexclean) whoami: $(shell whoami)" - rm -fr $(PAPIEX_SRC) - rm -f $(PAPIEX_SRC_TARBALL) $(PAPIEX_RELEASE) + - rm -rf src/vendor/papiex + - rm -rf src/epmt/lib # ----------- \end CLEANING ---------- # diff --git a/epmtdocs/docs/INSTALL.md b/epmtdocs/docs/INSTALL.md index 59b0e723f..0d34ba807 100644 --- a/epmtdocs/docs/INSTALL.md +++ b/epmtdocs/docs/INSTALL.md @@ -4,6 +4,67 @@ This is a tool to collect metadata and performance data about an entire job down The software contained in this repository was written by Philip Mucci of Minimal Metrics LLC. +## Installation via pip (recommended) + +EPMT can be installed directly with pip. When build tools (`gcc`, `make`, +`curl`) are present on the system, `pip install epmt` will automatically +compile the **papiex** C library and bundle the resulting shared libraries +into the installed package. If the build tools are missing, EPMT installs +cleanly as a pure-Python package and hardware counter collection is simply +unavailable at runtime (detectable with `epmt check`). + +### Quick install + +```bash +pip install epmt +``` + +### Install from source (with papiex compilation) + +```bash +# Clone the repository +git clone https://github.com/NOAA-GFDL/epmt.git +cd epmt + +# (Optional) Pre-download the papiex C source to avoid a network fetch +# at install time — useful in air-gapped environments. +make vendor-papiex + +# Install; setup.py will compile papiex via compile_papiex.sh +pip install ./src +``` + +### Build environment variables + +The following environment variables control how papiex is compiled during +`pip install`: + +| Variable | Default | Description | +|---|---|---| +| `PAPIEX_SRC_BRANCH` | `main` | Branch or tag of the papiex repository to download | +| `CONFIG_PAPIEX_PAPI` | `n` | Set to `y` to enable PAPI hardware counter support | +| `CONFIG_PAPIEX_DEBUG` | `n` | Set to `y` to enable a debug-mode build | + +Example: + +```bash +CONFIG_PAPIEX_PAPI=y pip install ./src +``` + +### Verify installation + +Run `epmt check` after installation to verify that papiex libraries are +present and that hardware counter collection is working: + +```bash +epmt check +``` + +If papiex was not compiled (e.g. because `gcc` was unavailable), `epmt check` +will report this and EPMT will still function for metadata collection. + +--- + ## Installation With Release File The release file includes EPMT, Data Collection Libraries, Notebook and EPMT Workflow GUI. diff --git a/src/MANIFEST.in b/src/MANIFEST.in index b746ac801..2484acf26 100644 --- a/src/MANIFEST.in +++ b/src/MANIFEST.in @@ -1,2 +1,3 @@ recursive-include papiex-epmt-install/lib *.so* recursive-include epmt/lib *.so* +include compile_papiex.sh diff --git a/src/compile_papiex.sh b/src/compile_papiex.sh new file mode 100755 index 000000000..11ede62ef --- /dev/null +++ b/src/compile_papiex.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# compile_papiex.sh — build papiex during pip install (best-effort) +# +# This script is invoked by setup.py during 'pip install' to compile the +# papiex C library and install its shared libraries into epmt/lib/ so they +# are picked up by the [tool.setuptools.package-data] glob "lib/*.so*". +# +# The script always exits 0 (graceful skip) when build prerequisites are +# missing or when the source cannot be obtained. It exits non-zero only +# when a compilation is attempted and fails; setup.py catches that case +# and prints a warning while allowing the install to proceed. +# +# Environment variables: +# PAPIEX_SRC_BRANCH - Branch/tag to download (default: main) +# CONFIG_PAPIEX_PAPI - Enable PAPI hardware counters (default: n) +# CONFIG_PAPIEX_DEBUG - Enable debug build (default: n) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Vendored source (populated by 'make vendor-papiex') +PAPIEX_VENDOR_SRC="${SCRIPT_DIR}/vendor/papiex" + +# Installation prefix: papiex installs libs into ${INSTALL_PREFIX}/lib/, +# which maps to src/epmt/lib/ and is picked up as package-data. +INSTALL_PREFIX="${SCRIPT_DIR}/epmt" + +PAPIEX_SRC_BRANCH="${PAPIEX_SRC_BRANCH:-main}" +PAPIEX_SRC_URL="https://github.com/NOAA-GFDL/papiex/archive/${PAPIEX_SRC_BRANCH}.tar.gz" +CONFIG_PAPIEX_PAPI="${CONFIG_PAPIEX_PAPI:-n}" +CONFIG_PAPIEX_DEBUG="${CONFIG_PAPIEX_DEBUG:-n}" + +echo "compile_papiex.sh: starting papiex build (best-effort)" +echo " SCRIPT_DIR=${SCRIPT_DIR}" +echo " INSTALL_PREFIX=${INSTALL_PREFIX}" + +# --- skip if shared libraries are already present (e.g. pre-bundled in sdist) --- +if ls "${INSTALL_PREFIX}/lib/"*.so* 2>/dev/null | head -1 | grep -q .; then + echo "compile_papiex.sh: papiex shared libraries already present in" \ + "${INSTALL_PREFIX}/lib/ — skipping compilation." + exit 0 +fi + +# --- check for required build tools --- +_missing="" +command -v gcc &>/dev/null || _missing="${_missing} gcc" +command -v make &>/dev/null || _missing="${_missing} make" +if [ -n "${_missing}" ]; then + echo "WARNING: the following build tools are not available:${_missing}" + echo " Skipping papiex compilation." + echo " EPMT will still install but hardware counter collection" + echo " will be unavailable. Run 'epmt check' to verify status." + exit 0 +fi + +# --- determine papiex source location --- +PAPIEX_SRC_DIR="" +PAPIEX_TMPDIR="" + +if [ -d "${PAPIEX_VENDOR_SRC}" ] && [ -f "${PAPIEX_VENDOR_SRC}/Makefile" ]; then + echo "compile_papiex.sh: using vendored papiex source at ${PAPIEX_VENDOR_SRC}" + PAPIEX_SRC_DIR="${PAPIEX_VENDOR_SRC}" +else + if ! command -v curl &>/dev/null; then + echo "WARNING: curl not found and no vendored papiex source available." + echo " Skipping papiex compilation." + exit 0 + fi + + echo "compile_papiex.sh: downloading papiex source from ${PAPIEX_SRC_URL}" + PAPIEX_TMPDIR="$(mktemp -d)" + # SC2064: intentional — capture PAPIEX_TMPDIR value now so the trap + # removes the correct directory even if the variable were later changed. + # shellcheck disable=SC2064 + trap 'rm -rf "${PAPIEX_TMPDIR}"' EXIT + + if ! curl -L --fail --retry 3 --retry-delay 5 \ + -o "${PAPIEX_TMPDIR}/papiex.tar.gz" \ + "${PAPIEX_SRC_URL}"; then + echo "WARNING: Failed to download papiex source from ${PAPIEX_SRC_URL}." + echo " Skipping papiex compilation." + exit 0 + fi + + # Determine the top-level directory name from the listing before + # extraction to avoid a second decompression pass. + TOP_DIR=$(tar -ztf "${PAPIEX_TMPDIR}/papiex.tar.gz" | head -1 | cut -d/ -f1) + tar -zxf "${PAPIEX_TMPDIR}/papiex.tar.gz" -C "${PAPIEX_TMPDIR}" + PAPIEX_SRC_DIR="${PAPIEX_TMPDIR}/${TOP_DIR}" +fi + +# --- compile and install --- +mkdir -p "${INSTALL_PREFIX}/lib" + +echo "compile_papiex.sh: compiling papiex" +echo " PAPIEX_SRC_DIR=${PAPIEX_SRC_DIR}" +echo " CONFIG_PAPIEX_PAPI=${CONFIG_PAPIEX_PAPI}" +echo " CONFIG_PAPIEX_DEBUG=${CONFIG_PAPIEX_DEBUG}" + +make -C "${PAPIEX_SRC_DIR}" \ + PREFIX="${INSTALL_PREFIX}" \ + CONFIG_PAPIEX_PAPI="${CONFIG_PAPIEX_PAPI}" \ + CONFIG_PAPIEX_DEBUG="${CONFIG_PAPIEX_DEBUG}" \ + install + +echo "compile_papiex.sh: papiex compiled and installed to ${INSTALL_PREFIX}" +exit 0 diff --git a/src/setup.py b/src/setup.py new file mode 100644 index 000000000..20994a5ed --- /dev/null +++ b/src/setup.py @@ -0,0 +1,47 @@ +"""Custom build step: compile papiex native libraries during pip install. + +Following the same pattern as NOAA-GFDL/pyFMS, this module hooks a shell +script into setuptools' build step so that papiex shared libraries are +compiled and placed in epmt/lib/ before the Python package is assembled. + +All package metadata (name, version, dependencies, …) lives in +pyproject.toml. This file contains only imperative build logic. +""" +import logging +import subprocess +from pathlib import Path + +from setuptools import setup +from setuptools.command.build import build + +logger = logging.getLogger(__name__) + + +class BuildPapiex(build): + """Compile papiex native libraries before the standard setuptools build.""" + + def run(self): + script = Path(__file__).parent / "compile_papiex.sh" + if script.exists(): + try: + with open("install_papiex.log", "w", encoding="utf-8") as log_fh: + subprocess.run( + [str(script)], + stdout=log_fh, + stderr=subprocess.STDOUT, + check=True, + ) + except subprocess.CalledProcessError: + logger.warning( + "papiex compilation failed. EPMT will still install " + "but hardware counter collection will be unavailable. " + "See install_papiex.log for details." + ) + except FileNotFoundError: + logger.warning( + "compile_papiex.sh not found — skipping papiex build." + ) + super().run() + + +setup(cmdclass={"build": BuildPapiex}) From 474468077fb8defbc281c2ec186c7c91837a8914 Mon Sep 17 00:00:00 2001 From: Ian Laflotte Date: Wed, 18 Mar 2026 09:54:15 -0400 Subject: [PATCH 03/12] make the pip install fail if compiling papiex does not work, remove subprocess stdout stderr piping which obscures good output in the github CI --- environment.yaml | 3 +-- src/setup.py | 10 ++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/environment.yaml b/environment.yaml index 91ed538af..55216bcca 100644 --- a/environment.yaml +++ b/environment.yaml @@ -19,7 +19,6 @@ name: epmt channels: - conda-forge - - defaults dependencies: # 3.9/3.10/3.11 supported; 3.12 requires pandas 2.x + SQLAlchemy 2.x (issue #105) @@ -50,4 +49,4 @@ dependencies: - pip - pip: - - -e src/ # editable install of epmt into this environment + - src/ # editable install of epmt into this environment diff --git a/src/setup.py b/src/setup.py index 20994a5ed..522b8c0a2 100644 --- a/src/setup.py +++ b/src/setup.py @@ -27,20 +27,22 @@ def run(self): with open("install_papiex.log", "w", encoding="utf-8") as log_fh: subprocess.run( [str(script)], - stdout=log_fh, - stderr=subprocess.STDOUT, +# stdout=subprocess.STDOUT,#log_fh, +# stderr=subprocess.STDERR, check=True, ) - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as e: logger.warning( "papiex compilation failed. EPMT will still install " "but hardware counter collection will be unavailable. " "See install_papiex.log for details." ) - except FileNotFoundError: + raise e + except FileNotFoundError as e: logger.warning( "compile_papiex.sh not found — skipping papiex build." ) + raise e super().run() From 51b2645904e1361636ca7d99c34b02495e8acc8e Mon Sep 17 00:00:00 2001 From: Ian Laflotte Date: Wed, 18 Mar 2026 14:34:16 -0400 Subject: [PATCH 04/12] i think this might work for at least the conda environment builds --- src/MANIFEST.in | 2 +- src/compile_papiex.sh | 4 +++- src/epmt/epmt_default_settings.py | 2 +- src/pyproject.toml | 1 + src/setup.py | 31 +++++++++++++++++++++---------- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/MANIFEST.in b/src/MANIFEST.in index 2484acf26..779964c0f 100644 --- a/src/MANIFEST.in +++ b/src/MANIFEST.in @@ -1,3 +1,3 @@ -recursive-include papiex-epmt-install/lib *.so* recursive-include epmt/lib *.so* +recursive-include epmt/bin * include compile_papiex.sh diff --git a/src/compile_papiex.sh b/src/compile_papiex.sh index 11ede62ef..94666fa9e 100755 --- a/src/compile_papiex.sh +++ b/src/compile_papiex.sh @@ -85,7 +85,9 @@ else # Determine the top-level directory name from the listing before # extraction to avoid a second decompression pass. - TOP_DIR=$(tar -ztf "${PAPIEX_TMPDIR}/papiex.tar.gz" | head -1 | cut -d/ -f1) + # Disable pipefail here: head -1 exits early, causing tar to receive + # SIGPIPE (exit 141), which pipefail would propagate as a script failure. + TOP_DIR=$(set +o pipefail; tar -ztf "${PAPIEX_TMPDIR}/papiex.tar.gz" | head -1 | cut -d/ -f1) tar -zxf "${PAPIEX_TMPDIR}/papiex.tar.gz" -C "${PAPIEX_TMPDIR}" PAPIEX_SRC_DIR="${PAPIEX_TMPDIR}/${TOP_DIR}" fi diff --git a/src/epmt/epmt_default_settings.py b/src/epmt/epmt_default_settings.py index 9255bdc25..d00eed44e 100644 --- a/src/epmt/epmt_default_settings.py +++ b/src/epmt/epmt_default_settings.py @@ -39,7 +39,7 @@ # input pattern must match both csv v1 and v2 filenames input_pattern = "*-papiex*.[ct]sv" -install_prefix = path.abspath(get_install_root() + "/../papiex-epmt-install/") +install_prefix = get_install_root() # install_prefix = path.dirname(path.abspath(__file__)) + "/../papiex-oss/papiex-epmt-install/" # install_prefix = path.abspath(this_file_dir + "/../../papiex-epmt-install/") diff --git a/src/pyproject.toml b/src/pyproject.toml index 32b7fd67e..06d3ed84d 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -110,6 +110,7 @@ where = ["."] [tool.setuptools.package-data] epmt = [ "lib/*.so*", + "bin/*", "alembic.ini", "preset_settings/*.py", diff --git a/src/setup.py b/src/setup.py index 522b8c0a2..ddb2c2634 100644 --- a/src/setup.py +++ b/src/setup.py @@ -16,6 +16,8 @@ logger = logging.getLogger(__name__) +LOG_FILE = "install_papiex.log" + class BuildPapiex(build): """Compile papiex native libraries before the standard setuptools build.""" @@ -24,26 +26,35 @@ def run(self): script = Path(__file__).parent / "compile_papiex.sh" if script.exists(): try: - with open("install_papiex.log", "w", encoding="utf-8") as log_fh: + with open(LOG_FILE, "w", encoding="utf-8") as log_fh: subprocess.run( [str(script)], -# stdout=subprocess.STDOUT,#log_fh, -# stderr=subprocess.STDERR, + stdout=log_fh, + stderr=subprocess.STDOUT, check=True, ) - except subprocess.CalledProcessError as e: + except subprocess.CalledProcessError as exc: logger.warning( - "papiex compilation failed. EPMT will still install " - "but hardware counter collection will be unavailable. " - "See install_papiex.log for details." + "papiex compilation failed (exit %d). EPMT will still " + "install but hardware counter collection will be " + "unavailable. Build log follows:\n%s", + exc.returncode, _read_log(), ) - raise e - except FileNotFoundError as e: + except FileNotFoundError: logger.warning( "compile_papiex.sh not found — skipping papiex build." ) - raise e + else: + logger.info("papiex build log:\n%s", _read_log()) super().run() +def _read_log(): + """Return the contents of the papiex build log, or a fallback message.""" + try: + return Path(LOG_FILE).read_text(encoding="utf-8", errors="replace") + except OSError: + return "(log file not available)" + + setup(cmdclass={"build": BuildPapiex}) From f8cf476089a45ef1956def3f5ea5b7a71cbab17d Mon Sep 17 00:00:00 2001 From: Ian Laflotte Date: Wed, 18 Mar 2026 14:43:57 -0400 Subject: [PATCH 05/12] more verbose pip install output, call epmt check and let it fail if it has to for now --- .github/workflows/build_and_test_epmt.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test_epmt.yml b/.github/workflows/build_and_test_epmt.yml index 1de519970..35a075c46 100644 --- a/.github/workflows/build_and_test_epmt.yml +++ b/.github/workflows/build_and_test_epmt.yml @@ -168,7 +168,7 @@ jobs: # Remove any pre-copied .so files to exercise the compilation path rm -rf src/epmt/lib/ # Install directly from source; setup.py invokes compile_papiex.sh - python3 -m pip install ./src + python3 -m pip install -v -v ./src SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])") if ls ${SITE_PACKAGES}/epmt/lib/*.so* 2>/dev/null | head -1 | grep -q .; then echo "papiex shared libraries found — compile_papiex.sh succeeded" @@ -176,6 +176,7 @@ jobs: echo "WARNING: papiex libraries not found after pip install ./src" echo " (compile_papiex.sh may have skipped or failed gracefully)" fi + epmt -v check || true # Uninstall before the sdist-based install below python3 -m pip uninstall -y epmt || true From 928557f6f06652ff2e0f346baba09d2a68d3cd41 Mon Sep 17 00:00:00 2001 From: Ian Laflotte Date: Wed, 18 Mar 2026 14:54:55 -0400 Subject: [PATCH 06/12] get rid of awful by-hand linking of papiex libraries/binaries --- .github/workflows/build_and_test_epmt.yml | 6 +----- .gitlab-ci.yml | 3 --- Dockerfiles/Dockerfile.centos-7-epmt-test-release | 3 --- Dockerfiles/Dockerfile.rocky-8-epmt-test-release | 2 -- 4 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/workflows/build_and_test_epmt.yml b/.github/workflows/build_and_test_epmt.yml index 35a075c46..bd8e6773e 100644 --- a/.github/workflows/build_and_test_epmt.yml +++ b/.github/workflows/build_and_test_epmt.yml @@ -189,12 +189,8 @@ jobs: ls src/dist/epmt*gz python3 -m pip install src/dist/epmt*gz - - name: create links to papiex executables, adjust path + - name: adjust path run: | - SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])") - mkdir -p ${SITE_PACKAGES}/papiex-epmt-install - ln -s ${SITE_PACKAGES}/epmt/lib ${SITE_PACKAGES}/papiex-epmt-install/ - ln -s ${SITE_PACKAGES}/epmt/bin ${SITE_PACKAGES}/papiex-epmt-install/ export PATH='/usr/bin:${PATH}:/usr/local/libexec:/usr/local/bin' - name: Configure epmt to use PostgreSQL diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fa4066606..b0e4b208f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,9 +36,6 @@ build:python3: - make dist python-dist dist-test - pip3 install src/dist/epmt*gz - - mkdir -p /usr/lib/python3.9/site-packages/papiex-epmt-install - - ln -s /usr/lib/python3.9/site-packages/epmt/lib /usr/lib/python3.9/site-packages/papiex-epmt-install/ - - ln -s /usr/lib/python3.9/site-packages/epmt/bin /usr/lib/python3.9/site-packages/papiex-epmt-install/ - export PATH='/usr/bin:${PATH}:/usr/local/bin:/usr/local/libexec' diff --git a/Dockerfiles/Dockerfile.centos-7-epmt-test-release b/Dockerfiles/Dockerfile.centos-7-epmt-test-release index 49ee4c197..65e8a9842 100644 --- a/Dockerfiles/Dockerfile.centos-7-epmt-test-release +++ b/Dockerfiles/Dockerfile.centos-7-epmt-test-release @@ -70,9 +70,6 @@ COPY ${epmt_full_release} . COPY src/dist/${epmt_python_full_release} . RUN python3 -m pip install ${epmt_python_full_release} -RUN mkdir /usr/lib/python3.9/site-packages/papiex-oss \ - && tar zxf ${epmt_python_full_release} \ - && mv epmt-${epmt_version}/papiex-epmt-install /usr/lib/python3.9/site-packages/papiex-oss # && rm -rf *.tgz COPY utils/epmt-installer . diff --git a/Dockerfiles/Dockerfile.rocky-8-epmt-test-release b/Dockerfiles/Dockerfile.rocky-8-epmt-test-release index 848e7dc18..72255fc5c 100644 --- a/Dockerfiles/Dockerfile.rocky-8-epmt-test-release +++ b/Dockerfiles/Dockerfile.rocky-8-epmt-test-release @@ -82,8 +82,6 @@ COPY src/dist/${epmt_python_full_release} . RUN python3 -m pip install ${epmt_python_full_release} -RUN tar zxf ${epmt_python_full_release} \ - && mv epmt-${epmt_version}/papiex-epmt-install /usr/lib/python3.9/site-packages/ # && rm -rf *.tgz COPY utils/epmt-installer . From 25aa63ef2dac1610027afe2719a53f2d54901804 Mon Sep 17 00:00:00 2001 From: Ian Laflotte Date: Wed, 18 Mar 2026 15:49:48 -0400 Subject: [PATCH 07/12] Remove OUTSIDE_DOCKER papiex path, show build output at install time Makefile: - Remove OUTSIDE_DOCKER branch from papiex-dist rule (docker-dist only) - Update python-dist comments to document Docker-only usage - Clean up extracted papiex-epmt-install/ dir after copying .so files build_and_test_epmt.yml: - Remove OUTSIDE_DOCKER papiex tarball cache/build steps - Replace sdist-based install with direct pip install ./src - Remove -v -v flags (output now visible by default) weekly_cache_builds.yml: - Remove OUTSIDE_DOCKER papiex build/verify/cache steps setup.py: - Tee compile_papiex.sh output to /dev/tty (bypasses pip capture) - Fall back to stderr when no TTY available (CI) - Always write to install_papiex.log .gitignore: - Add src/build/, src/epmt/bin/, src/epmt/include/ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build_and_test_epmt.yml | 35 +++---------- .github/workflows/weekly_cache_builds.yml | 21 ++------ .gitignore | 8 ++- Makefile | 23 ++++---- src/setup.py | 64 +++++++++++++++++------ 5 files changed, 72 insertions(+), 79 deletions(-) diff --git a/.github/workflows/build_and_test_epmt.yml b/.github/workflows/build_and_test_epmt.yml index bd8e6773e..9c1e44b2e 100644 --- a/.github/workflows/build_and_test_epmt.yml +++ b/.github/workflows/build_and_test_epmt.yml @@ -138,22 +138,10 @@ jobs: run: | make epmt-dash - # ── papiex tarball (OUTSIDE_DOCKER): cached across runs ──────────────── - # Cache key: version + OS target + "outside-docker" suffix to distinguish - # from the Docker-built variant cached by docker_build_test. - # The weekly_tarball_build workflow pre-warms this cache every Monday; - # the build below is a fallback for cache misses. - - name: Check Cache for papiex tarball (OUTSIDE_DOCKER) - id: cache-papiex - uses: actions/cache@v4 - with: - path: papiex-epmt-${{ env.PAPIEX_VERSION }}-${{ env.OS_TARGET }}.tgz - key: papiex-outside-docker-${{ env.PAPIEX_VERSION }}-${{ env.OS_TARGET }} - - - name: make papiex tarball - if: steps.cache-papiex.outputs.cache-hit != 'true' - run: | - make OUTSIDE_DOCKER='YUP' papiex-dist + # ── papiex compilation is handled by compile_papiex.sh at pip install time ── + # For bare-metal (non-Docker) installs, setup.py invokes compile_papiex.sh + # which downloads and compiles papiex from source. The Docker release path + # still uses the Makefile's papiex-dist target (via docker-dist). # ── Test pip install ./src with papiex compilation via compile_papiex.sh ── # This exercises the new setup.py hook: compile_papiex.sh downloads @@ -163,12 +151,12 @@ jobs: # compile_papiex.sh always exits 0 on graceful skips (missing tools, network # errors), and setup.py catches non-zero exits and warns without failing the # install — so pip install ./src should always succeed. - - name: Test pip install ./src (papiex compiled via compile_papiex.sh) + - name: pip install ./src (papiex compiled via compile_papiex.sh) run: | # Remove any pre-copied .so files to exercise the compilation path rm -rf src/epmt/lib/ # Install directly from source; setup.py invokes compile_papiex.sh - python3 -m pip install -v -v ./src + python3 -m pip install ./src SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])") if ls ${SITE_PACKAGES}/epmt/lib/*.so* 2>/dev/null | head -1 | grep -q .; then echo "papiex shared libraries found — compile_papiex.sh succeeded" @@ -177,17 +165,6 @@ jobs: echo " (compile_papiex.sh may have skipped or failed gracefully)" fi epmt -v check || true - # Uninstall before the sdist-based install below - python3 -m pip uninstall -y epmt || true - - - name: make epmt pip-packaging - run: | - make dist python-dist dist-test - - - name: pip install epmt pip-package - run: | - ls src/dist/epmt*gz - python3 -m pip install src/dist/epmt*gz - name: adjust path run: | diff --git a/.github/workflows/weekly_cache_builds.yml b/.github/workflows/weekly_cache_builds.yml index de4142be6..1e23fd2e2 100644 --- a/.github/workflows/weekly_cache_builds.yml +++ b/.github/workflows/weekly_cache_builds.yml @@ -187,24 +187,9 @@ jobs: path: src/epmt/ui key: epmt-dash-${{ env.EPMT_DASH_SRC_BRANCH }} - # ── papiex (OUTSIDE_DOCKER) ──────────────────────────────────── - - name: Build papiex-dist (OUTSIDE_DOCKER) - run: make OUTSIDE_DOCKER='YUP' papiex-dist - - - name: Verify papiex tarball exists - run: | - TARBALL="papiex-epmt-${PAPIEX_VERSION}-${OS_TARGET}.tgz" - if [ ! -f "${TARBALL}" ]; then - echo "::error::Papiex OUTSIDE_DOCKER tarball '${TARBALL}' not found." - exit 1 - fi - echo "Papiex OUTSIDE_DOCKER tarball '${TARBALL}' exists." - - - name: Cache papiex tarball (OUTSIDE_DOCKER) - uses: actions/cache/save@v4 - with: - path: papiex-epmt-${{ env.PAPIEX_VERSION }}-${{ env.OS_TARGET }}.tgz - key: papiex-outside-docker-${{ env.PAPIEX_VERSION }}-${{ env.OS_TARGET }} + # ── papiex (OUTSIDE_DOCKER) removed ───────────────────────── + # Bare-metal papiex compilation is now handled by compile_papiex.sh + # at pip install time. Only the Docker-based papiex cache remains. # ── Python + SQLite from source (container-path cache) ────────── # Pre-warm the Python/SQLite build cache so build_and_test_epmt diff --git a/.gitignore b/.gitignore index 1596e3104..d12cff473 100644 --- a/.gitignore +++ b/.gitignore @@ -72,7 +72,11 @@ src/epmt/lib/ # pip build log from setup.py install_papiex.log +# setuptools build artifacts +src/build/ +# papiex compilation byproducts (headers and helper scripts) +src/epmt/bin/ +src/epmt/include/ + # virtualenv venv* - - diff --git a/Makefile b/Makefile index bb42b069d..d7df284b6 100644 --- a/Makefile +++ b/Makefile @@ -161,10 +161,14 @@ $(EPMT_RELEASE) dist: @echo "**********************************************************" tar -czf $(EPMT_RELEASE) epmt-install -# runs setuptools -# note that this step requires EPMT_RELEASE and PAPIEX_RELEASE, but we don't explicitly state it here, b.c. -# when we go in the docker container, the time-zone changes and therefore thet timestamp comparison triggers re-making -# targets that do not need to be remade +# runs setuptools to build the sdist. +# Used in the Docker release flow: make papiex-dist (Docker path) builds the papiex +# tarball, then python-dist extracts the pre-compiled .so files into epmt/lib/ before +# building the sdist. For bare-metal installs, papiex is compiled at pip install time +# via compile_papiex.sh instead. +# NOTE: we don't explicitly list EPMT_RELEASE and PAPIEX_RELEASE as dependencies, b.c. +# when we go in the docker container, the time-zone changes and therefore the timestamp +# comparison triggers re-making targets that do not need to be remade python-dist: @echo "(python-dist) whoami: $(shell whoami)" @echo "**********************************************************" @@ -173,6 +177,7 @@ python-dist: cd src && echo "GOOD: cd src" || echo "I FAILED: cd src" ; \ tar zxf ../$(PAPIEX_RELEASE) && echo "GOOD: tar -zxf ../PAPIEX_RELEASE" || echo "I FAILED: tar zxf ../PAPIEX_RELEASE" ; \ mkdir -p epmt/lib && cp -a papiex-epmt-install/lib/*.so* epmt/lib/ && echo "GOOD: cp papiex libs to epmt/lib" || echo "I FAILED: cp papiex libs to epmt/lib" ; \ + rm -rf papiex-epmt-install && echo "GOOD: rm papiex-epmt-install" || echo "I FAILED: rm papiex-epmt-install" ; \ pip3 install --quiet build && echo "GOOD: pip3 install build" || echo "I FAILED: pip3 install build" ; \ python3 -m build --sdist && echo "GOOD: python3 -m build --sdist" || echo "I FAILED: python3 -m build --sdist" ; \ chmod a+r dist/* && echo "GOOD: chmod a+r dist/*" || echo "I FAILED: chmod a+r dist/*" @@ -282,15 +287,7 @@ $(PAPIEX_SRC)/$(PAPIEX_RELEASE): $(PAPIEX_SRC) @echo @echo @echo "################### BEGIN MAKE PAPIEX TARBALL : papiex-dist ########################################" - if [ -n "${OUTSIDE_DOCKER}" ]; then \ - echo "within docker. make and make check within PAPIEX_SRC/PAPIEX_RELEASE target" ; \ - make -C $(PAPIEX_SRC) CONFIG_PAPIEX_PAPI=$(CONFIG_PAPIEX_PAPI) CONFIG_PAPIEX_DEBUG=$(CONFIG_PAPIEX_DEBUG) OS_TARGET=$(OS_TARGET) distclean install dist ; \ - make -C $(PAPIEX_SRC) CONFIG_PAPIEX_PAPI=$(CONFIG_PAPIEX_PAPI) CONFIG_PAPIEX_DEBUG=$(CONFIG_PAPIEX_DEBUG) OS_TARGET=$(OS_TARGET) dist-test ; \ - make -C $(PAPIEX_SRC) CONFIG_PAPIEX_PAPI=$(CONFIG_PAPIEX_PAPI) CONFIG_PAPIEX_DEBUG=$(CONFIG_PAPIEX_DEBUG) OS_TARGET=$(OS_TARGET) check ; \ - else \ - echo "outside docker. making docker-dist within PAPIEX_SRC/PAPIEX_RELEASE target" ; \ - make -C $(PAPIEX_SRC) CONFIG_PAPIEX_PAPI=$(CONFIG_PAPIEX_PAPI) CONFIG_PAPIEX_DEBUG=$(CONFIG_PAPIEX_DEBUG) OS_TARGET=$(OS_TARGET) DOCKER_RUN_OPTS="$(DOCKER_RUN_OPTS)" docker-dist ; \ - fi + make -C $(PAPIEX_SRC) CONFIG_PAPIEX_PAPI=$(CONFIG_PAPIEX_PAPI) CONFIG_PAPIEX_DEBUG=$(CONFIG_PAPIEX_DEBUG) OS_TARGET=$(OS_TARGET) DOCKER_RUN_OPTS="$(DOCKER_RUN_OPTS)" docker-dist $(PAPIEX_SRC): $(PAPIEX_SRC_TARBALL) @echo "(PAPIEX_SRC) whoami: $(shell whoami)" diff --git a/src/setup.py b/src/setup.py index ddb2c2634..fb802302c 100644 --- a/src/setup.py +++ b/src/setup.py @@ -9,6 +9,7 @@ """ import logging import subprocess +import sys from pathlib import Path from setuptools import setup @@ -19,6 +20,14 @@ LOG_FILE = "install_papiex.log" +def _open_tty(): + """Open /dev/tty for direct user output, bypassing pip's capture.""" + try: + return open("/dev/tty", "w", encoding="utf-8") + except OSError: + return None + + class BuildPapiex(build): """Compile papiex native libraries before the standard setuptools build.""" @@ -26,35 +35,56 @@ def run(self): script = Path(__file__).parent / "compile_papiex.sh" if script.exists(): try: - with open(LOG_FILE, "w", encoding="utf-8") as log_fh: - subprocess.run( - [str(script)], - stdout=log_fh, - stderr=subprocess.STDOUT, - check=True, - ) + self._run_compile(script) except subprocess.CalledProcessError as exc: logger.warning( "papiex compilation failed (exit %d). EPMT will still " "install but hardware counter collection will be " - "unavailable. Build log follows:\n%s", - exc.returncode, _read_log(), + "unavailable. See %s for details.", + exc.returncode, LOG_FILE, ) except FileNotFoundError: logger.warning( "compile_papiex.sh not found — skipping papiex build." ) - else: - logger.info("papiex build log:\n%s", _read_log()) super().run() + @staticmethod + def _run_compile(script): + """Run compile_papiex.sh, teeing output to /dev/tty and a log file. -def _read_log(): - """Return the contents of the papiex build log, or a fallback message.""" - try: - return Path(LOG_FILE).read_text(encoding="utf-8", errors="replace") - except OSError: - return "(log file not available)" + pip suppresses build-backend stdout/stderr in non-verbose mode. + Writing directly to /dev/tty ensures the compilation progress is + always visible to interactive users. In non-interactive contexts + (CI, no TTY) the output still goes to the log file and stderr. + """ + tty_fh = _open_tty() + fallback_to_stderr = tty_fh is None + + proc = subprocess.Popen( + [str(script)], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + + with open(LOG_FILE, "w", encoding="utf-8") as log_fh: + for line in proc.stdout: + log_fh.write(line) + if tty_fh: + tty_fh.write(line) + tty_fh.flush() + elif fallback_to_stderr: + sys.stderr.write(line) + sys.stderr.flush() + + proc.wait() + + if tty_fh: + tty_fh.close() + + if proc.returncode != 0: + raise subprocess.CalledProcessError(proc.returncode, str(script)) setup(cmdclass={"build": BuildPapiex}) From 2572b13ec7f81579900edcec8cbe38cb11f0ee69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:56:38 +0000 Subject: [PATCH 08/12] feat: integrate papiex compilation into pip install via setup.py + compile_papiex.sh Co-authored-by: ilaflott <6273252+ilaflott@users.noreply.github.com> --- .github/workflows/build_and_test_epmt.yml | 24 +++++ .gitignore | 9 ++ Makefile | 31 ++++++- epmtdocs/docs/INSTALL.md | 61 ++++++++++++ src/MANIFEST.in | 1 + src/compile_papiex.sh | 108 ++++++++++++++++++++++ src/setup.py | 47 ++++++++++ 7 files changed, 280 insertions(+), 1 deletion(-) create mode 100755 src/compile_papiex.sh create mode 100644 src/setup.py diff --git a/.github/workflows/build_and_test_epmt.yml b/.github/workflows/build_and_test_epmt.yml index bddaf6cb4..ca5d6b1ed 100644 --- a/.github/workflows/build_and_test_epmt.yml +++ b/.github/workflows/build_and_test_epmt.yml @@ -155,6 +155,30 @@ jobs: run: | make OUTSIDE_DOCKER='YUP' papiex-dist + # ── Test pip install ./src with papiex compilation via compile_papiex.sh ── + # This exercises the new setup.py hook: compile_papiex.sh downloads + # papiex source from GitHub, compiles it, and installs .so files into + # src/epmt/lib/ so they are bundled into the pip package. + # gcc, make, and curl are already available from the earlier yum install step. + # compile_papiex.sh always exits 0 on graceful skips (missing tools, network + # errors), and setup.py catches non-zero exits and warns without failing the + # install — so pip install ./src should always succeed. + - name: Test pip install ./src (papiex compiled via compile_papiex.sh) + run: | + # Remove any pre-copied .so files to exercise the compilation path + rm -rf src/epmt/lib/ + # Install directly from source; setup.py invokes compile_papiex.sh + python3 -m pip install ./src + SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])") + if ls ${SITE_PACKAGES}/epmt/lib/*.so* 2>/dev/null | head -1 | grep -q .; then + echo "papiex shared libraries found — compile_papiex.sh succeeded" + else + echo "WARNING: papiex libraries not found after pip install ./src" + echo " (compile_papiex.sh may have skipped or failed gracefully)" + fi + # Uninstall before the sdist-based install below + python3 -m pip uninstall -y epmt || true + - name: make epmt pip-packaging run: | make dist python-dist dist-test diff --git a/.gitignore b/.gitignore index 6e93cd206..1596e3104 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,15 @@ src/epmt.egg-info/* src/papiex-epmt-install/* src/epmt/settings.py +# papiex compile_papiex.sh / pip install artifacts # +##################################################### +# vendored papiex C source (populated by 'make vendor-papiex') +src/vendor/ +# compiled papiex shared libraries (populated by compile_papiex.sh or make python-dist) +src/epmt/lib/ +# pip build log from setup.py +install_papiex.log + # virtualenv venv* diff --git a/Makefile b/Makefile index f4ab207cb..bb42b069d 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ PWD=$(shell pwd) install-py3-conda install-py3-pyenv install-deps \\ dist python-dist dist-test docker-dist docker-dist-test \\ epmt-dash \\ - papiex-dist \\ + papiex-dist vendor-papiex pip-install \\ epmt-full-release check-release \\ release \\ clean-extra clean-all clean distclean dashclean dockerclean papiexclean \\ @@ -305,6 +305,33 @@ $(PAPIEX_SRC_TARBALL): @echo "(PAPIEX_SRC_TARBALL) whoami: $(shell whoami)" curl -L --fail --retry 3 --retry-delay 5 -O $(PAPIEX_SRC_URL) ; \ ls $(PAPIEX_SRC_TARBALL) + +# Download papiex source into src/vendor/papiex/ so compile_papiex.sh +# can find it without needing network access at pip-install time. +vendor-papiex: + @echo "(vendor-papiex) whoami: $(shell whoami)" + @echo " ------ VENDOR PAPIEX SOURCE INTO src/vendor/papiex ------- " + @echo "PAPIEX_SRC_URL = ${PAPIEX_SRC_URL}" + if [ -d "src/vendor/papiex" ] && [ -f "src/vendor/papiex/Makefile" ]; then \ + echo "src/vendor/papiex already exists, skipping download." ; \ + else \ + mkdir -p src/vendor ; \ + curl -L --fail --retry 3 --retry-delay 5 \ + -o /tmp/papiex-vendor-$(PAPIEX_SRC_BRANCH).tar.gz $(PAPIEX_SRC_URL) ; \ + tar zxf /tmp/papiex-vendor-$(PAPIEX_SRC_BRANCH).tar.gz -C /tmp ; \ + TOP_DIR=$$(tar ztf /tmp/papiex-vendor-$(PAPIEX_SRC_BRANCH).tar.gz | head -1 | cut -d/ -f1) ; \ + mv /tmp/$${TOP_DIR} src/vendor/papiex ; \ + rm -f /tmp/papiex-vendor-$(PAPIEX_SRC_BRANCH).tar.gz ; \ + echo "papiex source vendored at src/vendor/papiex" ; \ + ls src/vendor/papiex ; \ + fi + +# Install epmt directly from src/ using pip, triggering compile_papiex.sh. +# Run 'make vendor-papiex' first to avoid a network download at install time. +pip-install: vendor-papiex + @echo "(pip-install) whoami: $(shell whoami)" + @echo " ------ PIP INSTALL epmt with PAPIEX COMPILATION ------- " + pip3 install ./src # ----------- \end PAPIEX THINGS ---------- # @@ -453,6 +480,8 @@ papiexclean: @echo "(papiexclean) whoami: $(shell whoami)" - rm -fr $(PAPIEX_SRC) - rm -f $(PAPIEX_SRC_TARBALL) $(PAPIEX_RELEASE) + - rm -rf src/vendor/papiex + - rm -rf src/epmt/lib # ----------- \end CLEANING ---------- # diff --git a/epmtdocs/docs/INSTALL.md b/epmtdocs/docs/INSTALL.md index 59b0e723f..0d34ba807 100644 --- a/epmtdocs/docs/INSTALL.md +++ b/epmtdocs/docs/INSTALL.md @@ -4,6 +4,67 @@ This is a tool to collect metadata and performance data about an entire job down The software contained in this repository was written by Philip Mucci of Minimal Metrics LLC. +## Installation via pip (recommended) + +EPMT can be installed directly with pip. When build tools (`gcc`, `make`, +`curl`) are present on the system, `pip install epmt` will automatically +compile the **papiex** C library and bundle the resulting shared libraries +into the installed package. If the build tools are missing, EPMT installs +cleanly as a pure-Python package and hardware counter collection is simply +unavailable at runtime (detectable with `epmt check`). + +### Quick install + +```bash +pip install epmt +``` + +### Install from source (with papiex compilation) + +```bash +# Clone the repository +git clone https://github.com/NOAA-GFDL/epmt.git +cd epmt + +# (Optional) Pre-download the papiex C source to avoid a network fetch +# at install time — useful in air-gapped environments. +make vendor-papiex + +# Install; setup.py will compile papiex via compile_papiex.sh +pip install ./src +``` + +### Build environment variables + +The following environment variables control how papiex is compiled during +`pip install`: + +| Variable | Default | Description | +|---|---|---| +| `PAPIEX_SRC_BRANCH` | `main` | Branch or tag of the papiex repository to download | +| `CONFIG_PAPIEX_PAPI` | `n` | Set to `y` to enable PAPI hardware counter support | +| `CONFIG_PAPIEX_DEBUG` | `n` | Set to `y` to enable a debug-mode build | + +Example: + +```bash +CONFIG_PAPIEX_PAPI=y pip install ./src +``` + +### Verify installation + +Run `epmt check` after installation to verify that papiex libraries are +present and that hardware counter collection is working: + +```bash +epmt check +``` + +If papiex was not compiled (e.g. because `gcc` was unavailable), `epmt check` +will report this and EPMT will still function for metadata collection. + +--- + ## Installation With Release File The release file includes EPMT, Data Collection Libraries, Notebook and EPMT Workflow GUI. diff --git a/src/MANIFEST.in b/src/MANIFEST.in index b746ac801..2484acf26 100644 --- a/src/MANIFEST.in +++ b/src/MANIFEST.in @@ -1,2 +1,3 @@ recursive-include papiex-epmt-install/lib *.so* recursive-include epmt/lib *.so* +include compile_papiex.sh diff --git a/src/compile_papiex.sh b/src/compile_papiex.sh new file mode 100755 index 000000000..11ede62ef --- /dev/null +++ b/src/compile_papiex.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# compile_papiex.sh — build papiex during pip install (best-effort) +# +# This script is invoked by setup.py during 'pip install' to compile the +# papiex C library and install its shared libraries into epmt/lib/ so they +# are picked up by the [tool.setuptools.package-data] glob "lib/*.so*". +# +# The script always exits 0 (graceful skip) when build prerequisites are +# missing or when the source cannot be obtained. It exits non-zero only +# when a compilation is attempted and fails; setup.py catches that case +# and prints a warning while allowing the install to proceed. +# +# Environment variables: +# PAPIEX_SRC_BRANCH - Branch/tag to download (default: main) +# CONFIG_PAPIEX_PAPI - Enable PAPI hardware counters (default: n) +# CONFIG_PAPIEX_DEBUG - Enable debug build (default: n) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Vendored source (populated by 'make vendor-papiex') +PAPIEX_VENDOR_SRC="${SCRIPT_DIR}/vendor/papiex" + +# Installation prefix: papiex installs libs into ${INSTALL_PREFIX}/lib/, +# which maps to src/epmt/lib/ and is picked up as package-data. +INSTALL_PREFIX="${SCRIPT_DIR}/epmt" + +PAPIEX_SRC_BRANCH="${PAPIEX_SRC_BRANCH:-main}" +PAPIEX_SRC_URL="https://github.com/NOAA-GFDL/papiex/archive/${PAPIEX_SRC_BRANCH}.tar.gz" +CONFIG_PAPIEX_PAPI="${CONFIG_PAPIEX_PAPI:-n}" +CONFIG_PAPIEX_DEBUG="${CONFIG_PAPIEX_DEBUG:-n}" + +echo "compile_papiex.sh: starting papiex build (best-effort)" +echo " SCRIPT_DIR=${SCRIPT_DIR}" +echo " INSTALL_PREFIX=${INSTALL_PREFIX}" + +# --- skip if shared libraries are already present (e.g. pre-bundled in sdist) --- +if ls "${INSTALL_PREFIX}/lib/"*.so* 2>/dev/null | head -1 | grep -q .; then + echo "compile_papiex.sh: papiex shared libraries already present in" \ + "${INSTALL_PREFIX}/lib/ — skipping compilation." + exit 0 +fi + +# --- check for required build tools --- +_missing="" +command -v gcc &>/dev/null || _missing="${_missing} gcc" +command -v make &>/dev/null || _missing="${_missing} make" +if [ -n "${_missing}" ]; then + echo "WARNING: the following build tools are not available:${_missing}" + echo " Skipping papiex compilation." + echo " EPMT will still install but hardware counter collection" + echo " will be unavailable. Run 'epmt check' to verify status." + exit 0 +fi + +# --- determine papiex source location --- +PAPIEX_SRC_DIR="" +PAPIEX_TMPDIR="" + +if [ -d "${PAPIEX_VENDOR_SRC}" ] && [ -f "${PAPIEX_VENDOR_SRC}/Makefile" ]; then + echo "compile_papiex.sh: using vendored papiex source at ${PAPIEX_VENDOR_SRC}" + PAPIEX_SRC_DIR="${PAPIEX_VENDOR_SRC}" +else + if ! command -v curl &>/dev/null; then + echo "WARNING: curl not found and no vendored papiex source available." + echo " Skipping papiex compilation." + exit 0 + fi + + echo "compile_papiex.sh: downloading papiex source from ${PAPIEX_SRC_URL}" + PAPIEX_TMPDIR="$(mktemp -d)" + # SC2064: intentional — capture PAPIEX_TMPDIR value now so the trap + # removes the correct directory even if the variable were later changed. + # shellcheck disable=SC2064 + trap 'rm -rf "${PAPIEX_TMPDIR}"' EXIT + + if ! curl -L --fail --retry 3 --retry-delay 5 \ + -o "${PAPIEX_TMPDIR}/papiex.tar.gz" \ + "${PAPIEX_SRC_URL}"; then + echo "WARNING: Failed to download papiex source from ${PAPIEX_SRC_URL}." + echo " Skipping papiex compilation." + exit 0 + fi + + # Determine the top-level directory name from the listing before + # extraction to avoid a second decompression pass. + TOP_DIR=$(tar -ztf "${PAPIEX_TMPDIR}/papiex.tar.gz" | head -1 | cut -d/ -f1) + tar -zxf "${PAPIEX_TMPDIR}/papiex.tar.gz" -C "${PAPIEX_TMPDIR}" + PAPIEX_SRC_DIR="${PAPIEX_TMPDIR}/${TOP_DIR}" +fi + +# --- compile and install --- +mkdir -p "${INSTALL_PREFIX}/lib" + +echo "compile_papiex.sh: compiling papiex" +echo " PAPIEX_SRC_DIR=${PAPIEX_SRC_DIR}" +echo " CONFIG_PAPIEX_PAPI=${CONFIG_PAPIEX_PAPI}" +echo " CONFIG_PAPIEX_DEBUG=${CONFIG_PAPIEX_DEBUG}" + +make -C "${PAPIEX_SRC_DIR}" \ + PREFIX="${INSTALL_PREFIX}" \ + CONFIG_PAPIEX_PAPI="${CONFIG_PAPIEX_PAPI}" \ + CONFIG_PAPIEX_DEBUG="${CONFIG_PAPIEX_DEBUG}" \ + install + +echo "compile_papiex.sh: papiex compiled and installed to ${INSTALL_PREFIX}" +exit 0 diff --git a/src/setup.py b/src/setup.py new file mode 100644 index 000000000..20994a5ed --- /dev/null +++ b/src/setup.py @@ -0,0 +1,47 @@ +"""Custom build step: compile papiex native libraries during pip install. + +Following the same pattern as NOAA-GFDL/pyFMS, this module hooks a shell +script into setuptools' build step so that papiex shared libraries are +compiled and placed in epmt/lib/ before the Python package is assembled. + +All package metadata (name, version, dependencies, …) lives in +pyproject.toml. This file contains only imperative build logic. +""" +import logging +import subprocess +from pathlib import Path + +from setuptools import setup +from setuptools.command.build import build + +logger = logging.getLogger(__name__) + + +class BuildPapiex(build): + """Compile papiex native libraries before the standard setuptools build.""" + + def run(self): + script = Path(__file__).parent / "compile_papiex.sh" + if script.exists(): + try: + with open("install_papiex.log", "w", encoding="utf-8") as log_fh: + subprocess.run( + [str(script)], + stdout=log_fh, + stderr=subprocess.STDOUT, + check=True, + ) + except subprocess.CalledProcessError: + logger.warning( + "papiex compilation failed. EPMT will still install " + "but hardware counter collection will be unavailable. " + "See install_papiex.log for details." + ) + except FileNotFoundError: + logger.warning( + "compile_papiex.sh not found — skipping papiex build." + ) + super().run() + + +setup(cmdclass={"build": BuildPapiex}) From a7743ffe9c61dfccf0eb24a9d369620b840c2c17 Mon Sep 17 00:00:00 2001 From: Ian Laflotte Date: Wed, 18 Mar 2026 09:54:15 -0400 Subject: [PATCH 09/12] make the pip install fail if compiling papiex does not work, remove subprocess stdout stderr piping which obscures good output in the github CI --- environment.yaml | 2 +- src/setup.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/environment.yaml b/environment.yaml index 26d1f4d8f..3ab75462f 100644 --- a/environment.yaml +++ b/environment.yaml @@ -78,4 +78,4 @@ dependencies: # - conda-forge::pyinstaller-hooks-contrib==2023.11 - pip: - - -e src/ # editable install of epmt into this environment + - src/ # editable install of epmt into this environment diff --git a/src/setup.py b/src/setup.py index 20994a5ed..522b8c0a2 100644 --- a/src/setup.py +++ b/src/setup.py @@ -27,20 +27,22 @@ def run(self): with open("install_papiex.log", "w", encoding="utf-8") as log_fh: subprocess.run( [str(script)], - stdout=log_fh, - stderr=subprocess.STDOUT, +# stdout=subprocess.STDOUT,#log_fh, +# stderr=subprocess.STDERR, check=True, ) - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as e: logger.warning( "papiex compilation failed. EPMT will still install " "but hardware counter collection will be unavailable. " "See install_papiex.log for details." ) - except FileNotFoundError: + raise e + except FileNotFoundError as e: logger.warning( "compile_papiex.sh not found — skipping papiex build." ) + raise e super().run() From c87c8cba3aa00d45a387c5f6cf2469429c586f88 Mon Sep 17 00:00:00 2001 From: Ian Laflotte Date: Wed, 18 Mar 2026 14:34:16 -0400 Subject: [PATCH 10/12] i think this might work for at least the conda environment builds --- src/MANIFEST.in | 2 +- src/compile_papiex.sh | 4 +++- src/epmt/epmt_default_settings.py | 2 +- src/pyproject.toml | 1 + src/setup.py | 31 +++++++++++++++++++++---------- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/MANIFEST.in b/src/MANIFEST.in index 2484acf26..779964c0f 100644 --- a/src/MANIFEST.in +++ b/src/MANIFEST.in @@ -1,3 +1,3 @@ -recursive-include papiex-epmt-install/lib *.so* recursive-include epmt/lib *.so* +recursive-include epmt/bin * include compile_papiex.sh diff --git a/src/compile_papiex.sh b/src/compile_papiex.sh index 11ede62ef..94666fa9e 100755 --- a/src/compile_papiex.sh +++ b/src/compile_papiex.sh @@ -85,7 +85,9 @@ else # Determine the top-level directory name from the listing before # extraction to avoid a second decompression pass. - TOP_DIR=$(tar -ztf "${PAPIEX_TMPDIR}/papiex.tar.gz" | head -1 | cut -d/ -f1) + # Disable pipefail here: head -1 exits early, causing tar to receive + # SIGPIPE (exit 141), which pipefail would propagate as a script failure. + TOP_DIR=$(set +o pipefail; tar -ztf "${PAPIEX_TMPDIR}/papiex.tar.gz" | head -1 | cut -d/ -f1) tar -zxf "${PAPIEX_TMPDIR}/papiex.tar.gz" -C "${PAPIEX_TMPDIR}" PAPIEX_SRC_DIR="${PAPIEX_TMPDIR}/${TOP_DIR}" fi diff --git a/src/epmt/epmt_default_settings.py b/src/epmt/epmt_default_settings.py index 3b76d9963..61a43c3d7 100644 --- a/src/epmt/epmt_default_settings.py +++ b/src/epmt/epmt_default_settings.py @@ -39,7 +39,7 @@ # input pattern must match both csv v1 and v2 filenames input_pattern = "*-papiex*.[ct]sv" -install_prefix = path.abspath(get_install_root() + "/../papiex-epmt-install/") +install_prefix = get_install_root() # install_prefix = path.dirname(path.abspath(__file__)) + "/../papiex-oss/papiex-epmt-install/" # install_prefix = path.abspath(this_file_dir + "/../../papiex-epmt-install/") diff --git a/src/pyproject.toml b/src/pyproject.toml index 51703da43..c3181c648 100644 --- a/src/pyproject.toml +++ b/src/pyproject.toml @@ -105,6 +105,7 @@ where = ["."] [tool.setuptools.package-data] epmt = [ "lib/*.so*", + "bin/*", "alembic.ini", "preset_settings/*.py", diff --git a/src/setup.py b/src/setup.py index 522b8c0a2..ddb2c2634 100644 --- a/src/setup.py +++ b/src/setup.py @@ -16,6 +16,8 @@ logger = logging.getLogger(__name__) +LOG_FILE = "install_papiex.log" + class BuildPapiex(build): """Compile papiex native libraries before the standard setuptools build.""" @@ -24,26 +26,35 @@ def run(self): script = Path(__file__).parent / "compile_papiex.sh" if script.exists(): try: - with open("install_papiex.log", "w", encoding="utf-8") as log_fh: + with open(LOG_FILE, "w", encoding="utf-8") as log_fh: subprocess.run( [str(script)], -# stdout=subprocess.STDOUT,#log_fh, -# stderr=subprocess.STDERR, + stdout=log_fh, + stderr=subprocess.STDOUT, check=True, ) - except subprocess.CalledProcessError as e: + except subprocess.CalledProcessError as exc: logger.warning( - "papiex compilation failed. EPMT will still install " - "but hardware counter collection will be unavailable. " - "See install_papiex.log for details." + "papiex compilation failed (exit %d). EPMT will still " + "install but hardware counter collection will be " + "unavailable. Build log follows:\n%s", + exc.returncode, _read_log(), ) - raise e - except FileNotFoundError as e: + except FileNotFoundError: logger.warning( "compile_papiex.sh not found — skipping papiex build." ) - raise e + else: + logger.info("papiex build log:\n%s", _read_log()) super().run() +def _read_log(): + """Return the contents of the papiex build log, or a fallback message.""" + try: + return Path(LOG_FILE).read_text(encoding="utf-8", errors="replace") + except OSError: + return "(log file not available)" + + setup(cmdclass={"build": BuildPapiex}) From e14eb616c7d7d9382fe6815bcf6a09b1678357fc Mon Sep 17 00:00:00 2001 From: Ian Laflotte Date: Wed, 18 Mar 2026 14:43:57 -0400 Subject: [PATCH 11/12] more verbose pip install output, call epmt check and let it fail if it has to for now --- .github/workflows/build_and_test_epmt.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test_epmt.yml b/.github/workflows/build_and_test_epmt.yml index ca5d6b1ed..0b8587534 100644 --- a/.github/workflows/build_and_test_epmt.yml +++ b/.github/workflows/build_and_test_epmt.yml @@ -168,7 +168,7 @@ jobs: # Remove any pre-copied .so files to exercise the compilation path rm -rf src/epmt/lib/ # Install directly from source; setup.py invokes compile_papiex.sh - python3 -m pip install ./src + python3 -m pip install -v -v ./src SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])") if ls ${SITE_PACKAGES}/epmt/lib/*.so* 2>/dev/null | head -1 | grep -q .; then echo "papiex shared libraries found — compile_papiex.sh succeeded" @@ -176,6 +176,7 @@ jobs: echo "WARNING: papiex libraries not found after pip install ./src" echo " (compile_papiex.sh may have skipped or failed gracefully)" fi + epmt -v check || true # Uninstall before the sdist-based install below python3 -m pip uninstall -y epmt || true From 52ac9f74adb35ff4046dd2ec620fa2aeeab98b9a Mon Sep 17 00:00:00 2001 From: Ian Laflotte Date: Wed, 18 Mar 2026 14:54:55 -0400 Subject: [PATCH 12/12] get rid of awful by-hand linking of papiex libraries/binaries --- .github/workflows/build_and_test_epmt.yml | 6 +----- .gitlab-ci.yml | 3 --- Dockerfiles/Dockerfile.centos-7-epmt-test-release | 3 --- Dockerfiles/Dockerfile.rocky-8-epmt-test-release | 2 -- 4 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/workflows/build_and_test_epmt.yml b/.github/workflows/build_and_test_epmt.yml index 0b8587534..4112031ac 100644 --- a/.github/workflows/build_and_test_epmt.yml +++ b/.github/workflows/build_and_test_epmt.yml @@ -189,12 +189,8 @@ jobs: ls src/dist/epmt*gz python3 -m pip install src/dist/epmt*gz - - name: create links to papiex executables, adjust path + - name: adjust path run: | - SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])") - mkdir -p ${SITE_PACKAGES}/papiex-epmt-install - ln -s ${SITE_PACKAGES}/epmt/lib ${SITE_PACKAGES}/papiex-epmt-install/ - ln -s ${SITE_PACKAGES}/epmt/bin ${SITE_PACKAGES}/papiex-epmt-install/ export PATH='/usr/bin:${PATH}:/usr/local/libexec:/usr/local/bin' - name: Configure epmt to use PostgreSQL diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fa4066606..b0e4b208f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,9 +36,6 @@ build:python3: - make dist python-dist dist-test - pip3 install src/dist/epmt*gz - - mkdir -p /usr/lib/python3.9/site-packages/papiex-epmt-install - - ln -s /usr/lib/python3.9/site-packages/epmt/lib /usr/lib/python3.9/site-packages/papiex-epmt-install/ - - ln -s /usr/lib/python3.9/site-packages/epmt/bin /usr/lib/python3.9/site-packages/papiex-epmt-install/ - export PATH='/usr/bin:${PATH}:/usr/local/bin:/usr/local/libexec' diff --git a/Dockerfiles/Dockerfile.centos-7-epmt-test-release b/Dockerfiles/Dockerfile.centos-7-epmt-test-release index 49ee4c197..65e8a9842 100644 --- a/Dockerfiles/Dockerfile.centos-7-epmt-test-release +++ b/Dockerfiles/Dockerfile.centos-7-epmt-test-release @@ -70,9 +70,6 @@ COPY ${epmt_full_release} . COPY src/dist/${epmt_python_full_release} . RUN python3 -m pip install ${epmt_python_full_release} -RUN mkdir /usr/lib/python3.9/site-packages/papiex-oss \ - && tar zxf ${epmt_python_full_release} \ - && mv epmt-${epmt_version}/papiex-epmt-install /usr/lib/python3.9/site-packages/papiex-oss # && rm -rf *.tgz COPY utils/epmt-installer . diff --git a/Dockerfiles/Dockerfile.rocky-8-epmt-test-release b/Dockerfiles/Dockerfile.rocky-8-epmt-test-release index 848e7dc18..72255fc5c 100644 --- a/Dockerfiles/Dockerfile.rocky-8-epmt-test-release +++ b/Dockerfiles/Dockerfile.rocky-8-epmt-test-release @@ -82,8 +82,6 @@ COPY src/dist/${epmt_python_full_release} . RUN python3 -m pip install ${epmt_python_full_release} -RUN tar zxf ${epmt_python_full_release} \ - && mv epmt-${epmt_version}/papiex-epmt-install /usr/lib/python3.9/site-packages/ # && rm -rf *.tgz COPY utils/epmt-installer .