From a7f6053605c2b15151fac8ef5a4f4c5e39562db0 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 27 Jul 2026 07:17:10 -0700 Subject: [PATCH 1/2] update the test python library and wheel builder --- .github/workflows/python-library.yml | 2 +- .github/workflows/wheels.yml | 210 +++++++++++++++++++++++++++ pyproject.toml | 51 ++++++- python/CMakeLists.txt | 4 +- 4 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/wheels.yml diff --git a/.github/workflows/python-library.yml b/.github/workflows/python-library.yml index ed43d5395..ac95aaedf 100644 --- a/.github/workflows/python-library.yml +++ b/.github/workflows/python-library.yml @@ -29,7 +29,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.12" + python-version: "3.13" - name: Install build dependencies shell: bash diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 000000000..a6f6e5506 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,210 @@ +name: Wheels + +on: + workflow_dispatch: + push: + branches: + - main + tags: + - v*.*.* + +jobs: + check_version: + name: Check Python version + runs-on: ubuntu-latest + outputs: + should_build: ${{ steps.version.outputs.should_build }} + version: ${{ steps.version.outputs.version }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Check whether the Python package version changed + id: version + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + REF: ${{ github.ref }} + BEFORE_SHA: ${{ github.event.before }} + run: | + python - <<'PY' + import os + import subprocess + import tomllib + + with open("pyproject.toml", "rb") as config: + current_version = tomllib.load(config)["project"]["version"] + + should_build = False + previous_version = None + before_sha = os.environ.get("BEFORE_SHA", "") + + if os.environ["EVENT_NAME"] == "workflow_dispatch": + should_build = True + elif os.environ["REF"].startswith("refs/tags/"): + should_build = True + elif os.environ["EVENT_NAME"] == "push" and os.environ["REF"] == "refs/heads/main": + if before_sha and set(before_sha) != {"0"}: + try: + previous_pyproject = subprocess.check_output( + ["git", "show", f"{before_sha}:pyproject.toml"], + text=True, + ) + previous_version = tomllib.loads(previous_pyproject)["project"]["version"] + should_build = current_version != previous_version + except subprocess.CalledProcessError: + should_build = True + else: + should_build = True + + print(f"current version: {current_version}") + if previous_version is not None: + print(f"previous version: {previous_version}") + print(f"should build: {should_build}") + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: + output.write(f"version={current_version}\n") + output.write(f"should_build={str(should_build).lower()}\n") + PY + + build_sdist: + name: Build SDist + needs: check_version + if: needs.check_version.outputs.should_build == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Build SDist + run: pipx run build --sdist + + - name: Check metadata + run: pipx run twine check dist/* + + - uses: actions/upload-artifact@v7 + with: + name: dist-sdist + path: dist/*.tar.gz + + + build_wheels: + name: Wheels on ${{ matrix.os }} + needs: check_version + if: needs.check_version.outputs.should_build == 'true' + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, macos-14, windows-latest] + + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - uses: pypa/cibuildwheel@v3.4 + + - name: Verify clean directory + run: git diff --exit-code + shell: bash + + - name: Upload wheels + uses: actions/upload-artifact@v7 + with: + path: wheelhouse/*.whl + name: dist-${{ matrix.os }} + + publish-to-testpypi: + name: Upload if release + needs: [check_version, build_wheels, build_sdist] + if: >- + github.event_name == 'push' && + github.ref == 'refs/heads/main' && + needs.check_version.outputs.should_build == 'true' + runs-on: ubuntu-latest + #if: github.event_name == 'release' && github.event.action == 'published' + environment: + name: testpypi + url: https://test.pypi.org/p/griddyn + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - uses: actions/setup-python@v6 + - uses: actions/download-artifact@v8 + with: + pattern: dist-* + merge-multiple: true + path: dist/ + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + repository-url: https://test.pypi.org/legacy/ + publish-to-pypi: + name: >- + Publish Python distribution to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/griddyn + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v8 + with: + pattern: dist-* + merge-multiple: true + path: dist/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + github-release: + name: >- + Sign the Python distribution with Sigstore + and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v8 + with: + pattern: dist-* + merge-multiple: true + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.3.0 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' dist/** + --repo '${{ github.repository }}' diff --git a/pyproject.toml b/pyproject.toml index d36b4445a..e22a720b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,25 +4,57 @@ build-backend = "scikit_build_core.build" [project] name = "griddyn" -version = "0.0.0" +version = "0.11.1" description = "Python bindings for GridDyn" readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.13" license = "BSD-3-Clause" license-files = ["LICENSE", "NOTICE"] authors = [ - { name = "GridDyn contributors" }, + { name = "Philip Top", email = "top1@llnl.gov" }, ] classifiers = [ "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.13", + "Operating System :: OS Independent", "Topic :: Scientific/Engineering", + "Development Status :: 4 - Beta", ] +keywords = [ + "power systems", + "simulation", + "grid dynamics", + "power flow", + "co-simulation", + "science", + "python", +] + +[project.urls] +Homepage = "https://github.com/LLNL/GridDyn" +Repository = "https://github.com/LLNL/GridDyn" +Issues = "https://github.com/LLNL/GridDyn/issues" +Changelog = "https://github.com/LLNL/GridDyn/blob/main/CHANGELOG.md" + +[project.optional-dependencies] +test = ["pytest"] [tool.scikit-build] +# Protect the configuration against future changes in scikit-build-core minimum-version = "build-system.requires" + +# Setuptools-style build caching in a local directory build-dir = "build/{wheel_tag}" + +# The build targets to use when building the project. Empty builds the default target. build.targets = ["griddyn_python_core"] + +# The components to install. If empty, all default components are installed. install.components = ["python"] + +# Build stable ABI wheels for CPython 3.13+ +wheel.py-api = "cp313" + cmake.version = ">=3.26.1" cmake.args = [ "-DGRIDDYN_BUILD_PYTHON_LIBRARY=ON", @@ -30,3 +62,16 @@ cmake.args = [ "-DGRIDDYN_ENABLE_FMI=OFF", "-DGRIDDYN_ENABLE_HELICS_EXECUTABLE=OFF", ] + +[tool.cibuildwheel] +# Necessary to see build output from the actual compilation +build-verbosity = 1 +skip = "pp311-macosx_arm64" + +# Run pytest to ensure that the package was correctly built +test-command = "pytest {project}/python/tests" +test-requires = "pytest" + +# Needed for C++23 support and consistency with rtunits macOS wheels. +[tool.cibuildwheel.macos.environment] +MACOSX_DEPLOYMENT_TARGET = "11.0" diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index a5ede0a59..2d8f271f9 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,5 +1,5 @@ find_package( - Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module + Python 3.13 REQUIRED COMPONENTS Interpreter Development.Module OPTIONAL_COMPONENTS Development.SABIModule ) @@ -18,7 +18,7 @@ endif() find_package(nanobind CONFIG REQUIRED) -nanobind_add_module(griddyn_python_core NB_STATIC griddyn_python.cpp) +nanobind_add_module(griddyn_python_core NB_STATIC STABLE_ABI griddyn_python.cpp) set(griddyn_python_warning_options $<$:-Wno-strict-aliasing -Wno-shadow> From 2644312ec6f53852bea6b578ab24e2467c8d62fe Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 27 Jul 2026 07:22:56 -0700 Subject: [PATCH 2/2] Potential fix for pull request finding 'CodeQL / Workflow does not contain permissions' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/wheels.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index a6f6e5506..976d8cf2b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -8,6 +8,9 @@ on: tags: - v*.*.* +permissions: + contents: read + jobs: check_version: name: Check Python version