Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ jobs:
run: |
mkdir -p release-dist/flepimop2-op_engine
cd flepimop2-op_engine
uv run --with build --with twine python -m build --outdir ../release-dist/flepimop2-op_engine
uv run --with twine python -m twine check --strict ../release-dist/flepimop2-op_engine/*
uv run --no-project --with build --with twine python -m build --outdir ../release-dist/flepimop2-op_engine
uv run --no-project --with twine python -m twine check --strict ../release-dist/flepimop2-op_engine/*
- name: 'Upload core package artifacts'
uses: 'actions/upload-artifact@v7'
with:
Expand Down
6 changes: 4 additions & 2 deletions docs/development/creating-a-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Today that means these two files must contain the same semantic version:

If any of them differ, the `validate` job fails immediately.

The workflow also validates that `flepimop2-op_engine` depends on the exact matching `op-engine` release version. For example, release `0.2.0` must declare `op-engine==0.2.0`.

## 2. Run The Local Release Preflight

Use the local pre-release target before dispatching the release workflow:
Expand Down Expand Up @@ -124,6 +126,6 @@ For `publish-target=pypi`, configure the PyPI trusted publisher entry for the sa

Because this repository publishes two distributions from one workflow, the same trusted publisher setup must be allowed to publish both package names on the selected index.

## 6. Current Packaging Note
## 6. Package Metadata

The release workflow now handles the mechanics of validating, building, and sequencing both distributions together. Package-index acceptance still depends on the metadata inside each built distribution, so publishing `flepimop2-op_engine` to a public index may still require follow-up packaging changes outside the workflow itself.
Published package metadata must use registry-compatible dependencies. The provider package must not declare direct URL dependencies, because PyPI rejects distributions that require them.
18 changes: 7 additions & 11 deletions flepimop2-op_engine/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "flepimop2-op_engine"
version = "0.1.0"
version = "0.1.1"
readme = { file = "README.md", content-type = "text/markdown" }
description = "flepimop2 engine provider package for op_engine"
requires-python = ">=3.11,<3.15"
Expand All @@ -12,12 +12,8 @@ authors = [
]
keywords = ["flepimop2", "op_engine", "engine", "provider"]
dependencies = [
# op-engine is not in a registry yet, so keep it as a direct reference.
"op-engine @ git+https://github.com/ACCIDDA/op_engine.git@main",

# flepimop2 is not in a registry, so keep it as a direct reference.
"flepimop2 @ git+https://github.com/ACCIDDA/flepimop2.git@main",

"op-engine==0.1.1",
"flepimop2>=0.1.0",
"pydantic>=2.0,<3",
"numpy>=1.26",
]
Expand All @@ -44,10 +40,6 @@ Issues = "https://github.com/ACCIDDA/op_engine/issues"
requires = ["hatchling"]
build-backend = "hatchling.build"

# REQUIRED for hatchling to accept git/direct URL dependencies
[tool.hatch.metadata]
allow-direct-references = true

# Ensure hatchling can build from src/ layout and include the namespace package
[tool.hatch.build.targets.wheel]
packages = ["src/flepimop2"]
Expand All @@ -57,6 +49,10 @@ testpaths = ["tests"]
addopts = "-q"


[tool.uv.sources]
op-engine = { path = "..", editable = true }


[tool.ruff.format]
docstring-code-format = true
docstring-code-line-length = 72
Expand Down
17 changes: 13 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ pytest-core:

# Assumes `flepimop2-op_engine/.venv` already exists (run `just provider-sync` or `just ci` first).
pytest-provider: provider-sync
cd flepimop2-op_engine && .venv/bin/python -m pytest --doctest-modules
#!/usr/bin/env bash
set -euo pipefail
CORE_DIST="$(mktemp -d)"
trap 'rm -rf "${CORE_DIST}"' EXIT
uv run --with build python -m build --wheel --outdir "${CORE_DIST}"
cd flepimop2-op_engine
export PATH="${PWD}/.venv/bin:${PATH}"
export PIP_FIND_LINKS="${CORE_DIST}"
.venv/bin/python -m pytest --doctest-modules

pytest: pytest-core pytest-provider

Expand Down Expand Up @@ -77,8 +85,8 @@ build-check-core:

build-check-provider:
cd flepimop2-op_engine && rm -rf dist
cd flepimop2-op_engine && uv run --with build --with twine python -m build --wheel
cd flepimop2-op_engine && uv run --with twine python -m twine check --strict dist/*
cd flepimop2-op_engine && uv run --no-project --with build --with twine python -m build --wheel
cd flepimop2-op_engine && uv run --no-project --with twine python -m twine check --strict dist/*

build-check: build-check-core build-check-provider

Expand All @@ -104,7 +112,7 @@ build-test-provider:
trap 'rm -rf "${CLEANROOM}"' EXIT
cd flepimop2-op_engine
uv export --frozen --only-group dev --no-emit-project --format requirements.txt --no-hashes --output-file "${CLEANROOM}/dev-requirements.txt" >/dev/null
uv run --with build python -m build --wheel --outdir "${CLEANROOM}/provider-dist"
uv run --no-project --with build python -m build --wheel --outdir "${CLEANROOM}/provider-dist"
cd ..
uv run --with build python -m build --wheel --outdir "${CLEANROOM}/core-dist"
uv venv --python "${UV_PYTHON_VERSION:-3.12}" "${CLEANROOM}/venv"
Expand All @@ -119,6 +127,7 @@ build-test-provider:
cp -R flepimop2-op_engine/tests "${CLEANROOM}/tests"
cd "${CLEANROOM}"
export PATH="${CLEANROOM}/venv/bin:${PATH}"
export PIP_FIND_LINKS="${CLEANROOM}/core-dist"
"${CLEANROOM}/venv/bin/pytest" --import-mode=importlib tests --quiet --exitfirst

build-test: build-test-core build-test-provider
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "op_engine"
version = "0.1.0"
version = "0.1.1"
description = "Multiphysics integration engine for ODEs and PDEs using implicit-explicit operator splitting"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11,<3.15"
Expand Down
42 changes: 39 additions & 3 deletions scripts/release_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

REPO_ROOT: Final[pathlib.Path] = pathlib.Path(__file__).resolve().parents[1]
SEMVER_PATTERN: Final[re.Pattern[str]] = re.compile(r"^\d+\.\d+\.\d+$")
PROVIDER_PYPROJECT: Final[pathlib.Path] = (
REPO_ROOT / "flepimop2-op_engine" / "pyproject.toml"
)


def get_declared_versions() -> dict[str, str]:
Expand All @@ -22,9 +25,9 @@ def get_declared_versions() -> dict[str, str]:
core_version = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text("utf-8"))[
"project"
]["version"]
provider_version = tomllib.loads(
(REPO_ROOT / "flepimop2-op_engine" / "pyproject.toml").read_text("utf-8")
)["project"]["version"]
provider_version = tomllib.loads(PROVIDER_PYPROJECT.read_text("utf-8"))["project"][
"version"
]

return {
"pyproject.toml": str(core_version),
Expand Down Expand Up @@ -56,6 +59,38 @@ def validate_release_version() -> str:
return version


def validate_provider_dependencies(version: str) -> None:
"""
Validate provider dependencies that must match release metadata.

Raises:
SystemExit: If any provider dependencies are direct references.
SystemExit: If the provider does not depend on the exact shared `op-engine`
release version.
"""
provider_project = tomllib.loads(PROVIDER_PYPROJECT.read_text("utf-8"))["project"]
dependencies = [str(dependency) for dependency in provider_project["dependencies"]]

direct_references = [
dependency
for dependency in dependencies
if " @ " in dependency or "://" in dependency or "git+" in dependency
]
if direct_references:
rendered = ", ".join(direct_references)
msg = f"Provider package dependencies must be publishable to PyPI: {rendered}"
raise SystemExit(msg)

expected_op_engine = f"op-engine=={version}"
if expected_op_engine not in dependencies:
rendered = ", ".join(dependencies)
msg = (
"Provider package must depend on the exact shared op-engine release "
f"version {expected_op_engine!r}; got: {rendered}"
)
raise SystemExit(msg)


def write_github_outputs(version: str, output_path: pathlib.Path) -> None:
"""Write workflow outputs for downstream GitHub Actions jobs."""
docs_version = ".".join(version.split(".")[:2])
Expand All @@ -78,6 +113,7 @@ def main() -> None:
args = parser.parse_args()

version = validate_release_version()
validate_provider_dependencies(version)
print(f"Validated release version: {version}")

output_path = args.github_output
Expand Down
Loading