Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3c31e87
fix: make OpenMP header optional
Luohaothu Feb 11, 2026
4963081
feat: migrate build and dependencies to conda-only workflow
Luohaothu Feb 11, 2026
c54b1e2
[CI] Code auto formatted
Feb 11, 2026
942a20d
fix: address package-config review feedback
Luohaothu Feb 11, 2026
30bbf00
ci: migrate workflows to conda-only variant matrix
Luohaothu Feb 11, 2026
879f074
ci: deduplicate workflow runs with concurrency groups
Luohaothu Feb 11, 2026
dd67226
ci: bootstrap conda build tooling and missing deps
Luohaothu Feb 11, 2026
5e9993a
ci: isolate workflow concurrency by event
Luohaothu Feb 11, 2026
07066b5
ci: switch default platform to linux-64
Luohaothu Feb 11, 2026
c8c1ce5
docs: annotate conda workflows for maintainability
Luohaothu Feb 11, 2026
e6be0f0
fix: enforce fail-fast dependency checks in conda ci
Luohaothu Feb 11, 2026
e081158
fix: avoid local croot channel in dependency resolution
Luohaothu Feb 11, 2026
9b52b0a
fix: stabilize conda matrix builds and source build compatibility
Luohaothu Feb 17, 2026
d204b31
fix: harden source-job matrix runner
Luohaothu Feb 17, 2026
42942b2
[CI] Code auto formatted
Feb 18, 2026
69e8065
fix: correct conda vscode preset generation
Luohaothu Feb 18, 2026
7050813
fix: unblock conda ci by making tecio optional
Luohaothu Feb 18, 2026
5a25186
Revert "fix: unblock conda ci by making tecio optional"
Luohaothu Feb 24, 2026
9260e19
fix: align tecio specs with new linux package names
Luohaothu Feb 24, 2026
633cf34
fix: handle missing git tags in cmake version detection
Luohaothu Feb 24, 2026
32027c0
fix: unblock build and conda consumer linkage
Luohaothu Feb 24, 2026
263e30c
fix: use exported version string in conda smoke test
Luohaothu Feb 24, 2026
34bf8ed
refactor: minimize levelset timestamp compatibility changes
Luohaothu Feb 24, 2026
7036555
[CI] Code auto formatted
Feb 24, 2026
c9e421b
fix: resolve review threads and align formatter output
Luohaothu Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
23 changes: 23 additions & 0 deletions .github/scripts/ci/bootstrap_conda_tools.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail

need_conda_build=0

if ! conda build --help >/dev/null 2>&1; then
need_conda_build=1
fi

if [[ $need_conda_build -eq 0 ]]; then
echo "conda-build is already available."
exit 0
fi

echo "Installing required conda tooling: conda-build"
conda install -y -n base -c conda-forge conda-build

if ! conda build --help >/dev/null 2>&1; then
echo "Failed to provision conda-build." >&2
exit 1
fi

echo "conda-build is ready."
107 changes: 107 additions & 0 deletions .github/scripts/ci/ensure_deps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env bash
set -euo pipefail

platform="osx-arm64"
mpi=""
openmp=""
owner="opflow-dev"
channels=()

while [[ $# -gt 0 ]]; do
case "$1" in
--platform)
platform="$2"
shift 2
;;
--mpi)
mpi="$2"
shift 2
;;
--openmp)
openmp="$2"
shift 2
;;
--owner)
owner="$2"
shift 2
;;
--channel)
channels+=("$2")
shift 2
;;
*)
echo "Unknown argument: $1" >&2
exit 2
;;
esac
done

if [[ -z "$mpi" || -z "$openmp" ]]; then
echo "Usage: ensure_deps.sh --mpi <nompi|openmpi> --openmp <on|off> [--platform <subdir>] [--owner <org>]" >&2
exit 2
fi

if [[ ${#channels[@]} -eq 0 ]]; then
channels=("$owner" "conda-forge")
else
has_owner=0
has_conda_forge=0
for c in "${channels[@]}"; do
[[ "$c" == "$owner" ]] && has_owner=1
[[ "$c" == "conda-forge" ]] && has_conda_forge=1
done
[[ $has_owner -eq 0 ]] && channels+=("$owner")
[[ $has_conda_forge -eq 0 ]] && channels+=("conda-forge")
fi

channel_args=()
for c in "${channels[@]}"; do
channel_args+=("-c" "$c")
done

missing=()

check_spec() {
local spec="$1"
if conda search --override-channels --platform "$platform" "${channel_args[@]}" "$spec" >/dev/null 2>&1; then
echo "[OK] $spec"
else
echo "[MISS] $spec"
missing+=("$spec")
fi
}

check_spec "cmake >=4.0.2"
check_spec "ninja"
check_spec "spdlog"
check_spec "tbb"
check_spec "${owner}::amgcl"
check_spec "vtk"
check_spec "benchmark"
check_spec "gtest"

if [[ "$mpi" == "nompi" ]]; then
check_spec "mpi * mpi_serial"
check_spec "${owner}::tecio"
check_spec "hdf5 * nompi*"
else
check_spec "mpi * openmpi"
check_spec "openmpi"
check_spec "${owner}::tecio-mpi * mpi_${mpi}_*"
check_spec "hdf5 * mpi_openmpi*"
fi

check_spec "${owner}::hypre * mpi_${mpi}_openmp_${openmp}_*"

if [[ "$openmp" == "on" && "$(uname -s)" == "Darwin" ]]; then
check_spec "llvm-openmp"
fi

if [[ ${#missing[@]} -gt 0 ]]; then
echo "Dependency check failed for platform=${platform}, mpi=${mpi}, openmp=${openmp}" >&2
printf 'Missing specs:\n' >&2
printf ' - %s\n' "${missing[@]}" >&2
exit 1
fi

echo "All dependencies available for platform=${platform}, mpi=${mpi}, openmp=${openmp}."
178 changes: 178 additions & 0 deletions .github/scripts/ci/generate_matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""Generate GitHub Actions matrix JSON from conda_build_config.yaml."""

from __future__ import annotations

import argparse
import json
import pathlib
import re
import sys
from typing import Dict, List

RUNNER_BY_PLATFORM = {
"osx-arm64": "macos-14",
"linux-64": "ubuntu-24.04",
}


def parse_conda_build_config(path: pathlib.Path) -> Dict[str, List[str]]:
data: Dict[str, List[str]] = {}
current: str | None = None

for raw_line in path.read_text(encoding="utf-8").splitlines():
line = raw_line.split("#", 1)[0].rstrip()
if not line.strip():
continue

key_match = re.match(r"^([A-Za-z0-9_-]+):\s*$", line)
if key_match:
current = key_match.group(1)
data[current] = []
continue

value_match = re.match(r"^\s*-\s*(.+?)\s*$", line)
if value_match and current is not None:
data[current].append(value_match.group(1))

missing = [name for name in ("mpi", "openmp") if not data.get(name)]
if missing:
raise ValueError(
f"Missing required variant keys in {path}: {', '.join(missing)}"
)

return data


def parse_platforms(raw: str) -> List[str]:
platforms = [x.strip() for x in raw.split(",") if x.strip()]
if not platforms:
raise ValueError("No platform selected")

unknown = [p for p in platforms if p not in RUNNER_BY_PLATFORM]
if unknown:
raise ValueError(
"Unsupported platform(s): "
+ ", ".join(unknown)
+ ". Supported: "
+ ", ".join(sorted(RUNNER_BY_PLATFORM))
)

return platforms


def package_matrix(
variants: Dict[str, List[str]], platforms: List[str]
) -> Dict[str, List[Dict[str, str]]]:
include: List[Dict[str, str]] = []
for platform in platforms:
runner = RUNNER_BY_PLATFORM[platform]
for mpi in variants["mpi"]:
for openmp in variants["openmp"]:
include.append(
{
"platform": platform,
"runner": runner,
"mpi": mpi,
"openmp": openmp,
}
)
return {"include": include}


def source_matrix(
variants: Dict[str, List[str]], build_types: List[str], platforms: List[str]
) -> Dict[str, List[Dict[str, str]]]:
include: List[Dict[str, str]] = []
for platform in platforms:
runner = RUNNER_BY_PLATFORM[platform]
for mpi in variants["mpi"]:
for openmp in variants["openmp"]:
for build_type in build_types:
include.append(
{
"platform": platform,
"runner": runner,
"mpi": mpi,
"openmp": openmp,
"build_type": build_type,
}
)
return {"include": include}


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument(
"--recipe-config",
default="conda/recipe/conda_build_config.yaml",
help="Path to conda build variant config",
)
parser.add_argument(
"--mode",
choices=["all", "package", "source"],
default="all",
help="Which matrix to generate",
)
parser.add_argument(
"--build-types",
default="Debug,Release",
help="Comma-separated build types for source matrix",
)
parser.add_argument(
"--platforms",
default="linux-64",
help="Comma-separated conda platform subdirs, e.g. osx-arm64,linux-64",
)
parser.add_argument(
"--github-output",
default="",
help="If set, write output key=value pairs for GitHub Actions",
)
return parser.parse_args()


def main() -> int:
args = parse_args()
config_path = pathlib.Path(args.recipe_config)
if not config_path.exists():
print(f"error: config file not found: {config_path}", file=sys.stderr)
return 1

build_types = [x.strip() for x in args.build_types.split(",") if x.strip()]
if not build_types:
print("error: --build-types resolved to empty list", file=sys.stderr)
return 1

try:
platforms = parse_platforms(args.platforms)
except ValueError as exc:
print(f"error: {exc}", file=sys.stderr)
return 1

variants = parse_conda_build_config(config_path)

payloads: Dict[str, Dict[str, List[Dict[str, str]]]] = {}
if args.mode in {"all", "package"}:
payloads["package_matrix"] = package_matrix(variants, platforms)
if args.mode in {"all", "source"}:
payloads["source_matrix"] = source_matrix(variants, build_types, platforms)

if args.github_output:
out_path = pathlib.Path(args.github_output)
with out_path.open("a", encoding="utf-8") as f:
for key, value in payloads.items():
f.write(f"{key}={json.dumps(value, separators=(',', ':'))}\n")

if args.mode == "all":
print(json.dumps(payloads, indent=2, sort_keys=True))
elif args.mode == "package":
print(json.dumps(payloads["package_matrix"], indent=2, sort_keys=True))
else:
print(json.dumps(payloads["source_matrix"], indent=2, sort_keys=True))

return 0


if __name__ == "__main__":
raise SystemExit(main())
47 changes: 47 additions & 0 deletions .github/scripts/ci/mpiexec-singleton.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail

# Fallback launcher for environments where OpenMPI cannot spawn MPI processes
# reliably (e.g., local hostname is not resolvable). It strips mpiexec options
# and runs the target command directly in singleton mode.

if [[ $# -eq 0 ]]; then
echo "Usage: mpiexec-singleton.sh <mpiexec args> <command>" >&2
exit 2
fi

args=("$@")
argc=$#
idx=0

while [[ $idx -lt $argc ]]; do
arg="${args[$idx]}"
case "$arg" in
-n|-np|--np|--n)
idx=$((idx + 2))
;;
--host|--hostfile|--bind-to|--map-by|--mca)
idx=$((idx + 2))
;;
--oversubscribe|--report-bindings)
idx=$((idx + 1))
;;
--)
idx=$((idx + 1))
break
;;
-*)
idx=$((idx + 1))
;;
*)
break
;;
esac
done

if [[ $idx -ge $argc ]]; then
echo "No executable found in mpiexec invocation: $*" >&2
exit 2
fi

exec "${args[@]:$idx}"
Loading
Loading