Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ steps:
agents:
queue: "juliagpu"
cuda: "*"
if: build.message !~ /\[skip tests\]/
if: build.message !~ /\[skip tests\]/ && (build.branch == "main" || build.pull_request.base_branch == "main" || build.message !~ /\[skip jll\]/)
timeout_in_minutes: 44
env:
LD_LIBRARY_PATH: ""
Expand Down
1 change: 0 additions & 1 deletion .githash

This file was deleted.

226 changes: 226 additions & 0 deletions .github/scripts/check_versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
#!/usr/bin/env python3
"""Version consistency check for pull requests targeting main."""

import argparse
import re
import subprocess
import sys
from pathlib import Path

REPO_ROOT = Path(subprocess.run(
["git", "rev-parse", "--show-toplevel"],
capture_output=True, text=True, check=True,
).stdout.strip())

# To reuse this script for another package (e.g. Legate.jl), change only this block.

PACKAGE_NAME = "cuNumeric"

WRAPPER_VERSION_FILE = "lib/cunumeric_jl_wrapper/VERSION"
WRAPPER_JLL_COMPAT_KEY = "cunumeric_jl_wrapper_jll"
WRAPPER_SRC_PREFIXES = (
"lib/cunumeric_jl_wrapper/src/",
"lib/cunumeric_jl_wrapper/include/",
"lib/cunumeric_jl_wrapper/CMakeLists.txt",
)

SUBPKG_TOML_PATH = "lib/CNPreferences/Project.toml"
SUBPKG_COMPAT_KEY = "CNPreferences"
SUBPKG_SRC_PREFIXES = (
"lib/CNPreferences/src/",
)


def parse_version(v: str) -> tuple:
return tuple(int(x) for x in v.strip().split("."))


def version_gt(a: str, b: str) -> bool:
return parse_version(a) > parse_version(b)


def git_show(ref: str, path: str) -> str:
result = subprocess.run(
["git", "show", f"{ref}:{path}"],
capture_output=True, text=True, check=True,
cwd=REPO_ROOT,
)
return result.stdout


def changed_files(base_ref: str) -> list:
result = subprocess.run(
["git", "diff", "--name-only", f"{base_ref}...HEAD"],
capture_output=True, text=True, check=True,
cwd=REPO_ROOT,
)
return result.stdout.splitlines()


def parse_top_level_version(toml_text: str) -> str:
for line in toml_text.splitlines():
stripped = line.strip()
if re.match(r"^\[", stripped):
break
m = re.match(r'^version\s*=\s*"([^"]+)"', stripped)
if m:
return m.group(1)
raise ValueError("Could not find top-level version in TOML")


def parse_compat_section(toml_text: str) -> dict:
in_compat = False
result = {}
for line in toml_text.splitlines():
stripped = line.strip()
if re.match(r"^\[", stripped):
in_compat = stripped == "[compat]"
continue
if in_compat and stripped and not stripped.startswith("#"):
m = re.match(r'^([\w_-]+)\s*=\s*"([^"]+)"', stripped)
if m:
result[m.group(1)] = m.group(2)
return result


def check_package_version(base_ref: str, pr_toml: str, errors: list):
main_toml = git_show(base_ref, "Project.toml")
main_ver = parse_top_level_version(main_toml)
pr_ver = parse_top_level_version(pr_toml)

print(f"\t[{PACKAGE_NAME}]\tmain={main_ver}\tpr={pr_ver}")
if not version_gt(pr_ver, main_ver):
errors.append(
f"{PACKAGE_NAME} version must be greater than main.\n"
f"\tmain: {main_ver} → pr: {pr_ver}\n"
f"\tBump the version in Project.toml (patch, minor, or major)."
)
else:
print(f"\t\tOK ({main_ver} → {pr_ver})")


def check_wrapper_version(base_ref: str, changed: list, errors: list):
src_changed = [f for f in changed if any(f.startswith(p) for p in WRAPPER_SRC_PREFIXES)]
if not src_changed:
print("\t[wrapper]\tno source changes — skipping")
return

print(f"\t[wrapper]\t{len(src_changed)} source file(s) changed:")
for f in src_changed:
print(f"\t\t{f}")

pr_ver = (REPO_ROOT / WRAPPER_VERSION_FILE).read_text().strip()
try:
main_ver = git_show(base_ref, WRAPPER_VERSION_FILE).strip()
except subprocess.CalledProcessError:
main_ver = "0.0.0"

print(f"\t\tVERSION\tmain={main_ver}\tpr={pr_ver}")
if not version_gt(pr_ver, main_ver):
errors.append(
f"{WRAPPER_VERSION_FILE} must be incremented when wrapper source changes.\n"
f"\tmain: {main_ver} → pr: {pr_ver}\n"
f"\tBump {WRAPPER_VERSION_FILE}."
)
else:
print(f"\t\tOK VERSION ({main_ver} → {pr_ver})")


def check_wrapper_compat_sync(pr_toml: str, errors: list):
wrapper_ver = (REPO_ROOT / WRAPPER_VERSION_FILE).read_text().strip()
compat_ver = parse_compat_section(pr_toml).get(WRAPPER_JLL_COMPAT_KEY)

lhs = WRAPPER_VERSION_FILE
rhs = f"Project.toml [compat] {WRAPPER_JLL_COMPAT_KEY}"
w = max(len(lhs), len(rhs))
print(f"\t[wrapper compat sync]")
print(f"\t\t{lhs:<{w}} = {wrapper_ver}")
print(f"\t\t{rhs:<{w}} = {compat_ver}")
if compat_ver is None:
errors.append(
f"{WRAPPER_JLL_COMPAT_KEY} not found in Project.toml [compat]."
)
elif compat_ver != wrapper_ver:
errors.append(
f"Project.toml [compat] {WRAPPER_JLL_COMPAT_KEY}={compat_ver} does not match {WRAPPER_VERSION_FILE}={wrapper_ver}.\n"
f"\tSet {WRAPPER_JLL_COMPAT_KEY} = \"{wrapper_ver}\" in Project.toml [compat]."
)
else:
print(f"\t\tOK")


def check_subpkg_version(base_ref: str, pr_toml: str, changed: list, errors: list):
src_changed = [f for f in changed if any(f.startswith(p) for p in SUBPKG_SRC_PREFIXES)]
if not src_changed:
print(f"\t[{SUBPKG_COMPAT_KEY}]\tno source changes — skipping")
return

print(f"\t[{SUBPKG_COMPAT_KEY}]\t{len(src_changed)} source file(s) changed:")
for f in src_changed:
print(f"\t\t{f}")

pr_subpkg_toml = (REPO_ROOT / SUBPKG_TOML_PATH).read_text()
try:
main_subpkg_toml = git_show(base_ref, SUBPKG_TOML_PATH)
except subprocess.CalledProcessError:
main_subpkg_toml = 'version = "0.0.0"'

pr_ver = parse_top_level_version(pr_subpkg_toml)
main_ver = parse_top_level_version(main_subpkg_toml)

print(f"\t\tversion\tmain={main_ver}\tpr={pr_ver}")
if not version_gt(pr_ver, main_ver):
errors.append(
f"{SUBPKG_TOML_PATH} version must be incremented when {SUBPKG_COMPAT_KEY} source changes.\n"
f"\tmain: {main_ver} → pr: {pr_ver}\n"
f"\tBump the version in {SUBPKG_TOML_PATH}."
)
else:
print(f"\t\tOK version ({main_ver} → {pr_ver})")

compat_ver = parse_compat_section(pr_toml).get(SUBPKG_COMPAT_KEY)
if compat_ver is None:
errors.append(
f"{SUBPKG_COMPAT_KEY} not found in Project.toml [compat]."
)
elif compat_ver != pr_ver:
errors.append(
f"Project.toml [compat] {SUBPKG_COMPAT_KEY}={compat_ver} does not match {SUBPKG_TOML_PATH} version={pr_ver}.\n"
f"\tSet {SUBPKG_COMPAT_KEY} = \"{pr_ver}\" in Project.toml [compat]."
)
else:
print(f"\t\tOK compat {SUBPKG_COMPAT_KEY}={compat_ver}")


def main():
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--base-ref", default="origin/main",
help="Git ref to compare against (default: origin/main)")
args = parser.parse_args()

base_ref = args.base_ref
errors = []

pr_toml = (REPO_ROOT / "Project.toml").read_text()
changed = changed_files(base_ref)

print("Version check")
print("─" * 60)
check_package_version(base_ref, pr_toml, errors)
check_wrapper_version(base_ref, changed, errors)
check_wrapper_compat_sync(pr_toml, errors)
check_subpkg_version(base_ref, pr_toml, changed, errors)
print("─" * 60)

if errors:
print(f"\nFAILED — {len(errors)} error(s):\n")
for i, e in enumerate(errors, 1):
print(f"\t{i}. {e}\n")
sys.exit(1)
else:
print("\nPASSED")


if __name__ == "__main__":
main()
54 changes: 50 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ on:
- 'deps/build.jl'
- 'Project.toml'
- 'lib/CNPreferences/src/**'
- '.github/workflows/ci.yml'
- 'Dockerfile' # container.yml depends on this
tags:
- 'v*'
branches:
Expand All @@ -32,11 +30,28 @@ on:
- 'deps/build.jl'
- 'Project.toml'
- 'lib/CNPreferences/src/**'
- '.github/workflows/ci.yml'
- 'Dockerfile' # container.yml depends on this
jobs:
pkg_resolve:
uses: ./.github/workflows/pkg_resolve.yml

check_changes:
name: Check for wrapper changes
runs-on: ubuntu-latest
outputs:
wrapper_changed: ${{ steps.filter.outputs.wrapper }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
wrapper:
- 'lib/cunumeric_jl_wrapper/**'

test:
name: Julia ${{ matrix.julia }} - ${{ matrix.os }}
needs: [pkg_resolve, check_changes]
if: ${{ github.base_ref == 'main' || needs.check_changes.outputs.wrapper_changed != 'true' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand All @@ -61,6 +76,37 @@ jobs:

- uses: julia-actions/cache@v2

- name: Detect Legate branch override
id: legate-branch
run: |
MSG=$(printf '%s\n%s' \
"${{ github.event.head_commit.message }}" \
"${{ github.event.pull_request.body }}")
BRANCH=$(echo "$MSG" | grep -oiP 'legate[-_]branch:\s*\K\S+' | head -1 | tr -d '[]' || true)
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
if [[ -n "$BRANCH" ]]; then
echo "Using Legate.jl branch override: $BRANCH"
else
echo "No Legate.jl branch override — using JLL"
fi

- name: Checkout Legate.jl branch override
if: steps.legate-branch.outputs.branch != ''
uses: actions/checkout@v4
with:
repository: ${{ github.repository_owner }}/Legate.jl
ref: ${{ steps.legate-branch.outputs.branch }}
path: Legate.jl

- name: Develop Legate.jl branch override
if: steps.legate-branch.outputs.branch != ''
run: |
julia --color=yes -e '
using Pkg;
Pkg.develop(PackageSpec(path = "Legate.jl/lib/LegatePreferences"))
Pkg.develop(PackageSpec(path = "Legate.jl"))
'

- name: Run tests
env:
GPUTESTS: "0" # parsed by runtests.jl
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ on:
- main
jobs:
push_to_registry:
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
name: Container for ${{ matrix.platform }} - Julia ${{ matrix.julia }} - CUDA ${{ matrix.cuda }}
permissions:
contents: read
Expand Down
43 changes: 41 additions & 2 deletions .github/workflows/developer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ on:
- 'lib/CNPreferences/src/**'
- '.github/workflows/developer.yml'
jobs:
pkg_resolve:
uses: ./.github/workflows/pkg_resolve.yml

docs:
name : Developer CI test
name: Developer CI test
needs: pkg_resolve
permissions:
contents: read
packages: write
Expand All @@ -55,7 +59,7 @@ jobs:
shell: bash
env:
LEGATE_AUTO_CONFIG: 0
NO_CUDA: ON
LEGATE_WRAPPER_ENABLE_CUDA: OFF
steps:
- name: Check out the repo
uses: actions/checkout@v4
Expand Down Expand Up @@ -87,6 +91,36 @@ jobs:
with:
cache-name: julia-developer-ci

# Parse commit message or PR body for [legate-branch: <branch>].
# Example: include "[legate-branch: my-feature]" anywhere in the message.
- name: Detect Legate branch override
id: legate-branch
run: |
MSG=$(printf '%s\n%s' \
"${{ github.event.head_commit.message }}" \
"${{ github.event.pull_request.body }}")
BRANCH=$(echo "$MSG" | grep -oiP 'legate[-_]branch:\s*\K\S+' | head -1 | tr -d '[]' || true)
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
if [[ -n "$BRANCH" ]]; then
echo "Using Legate.jl branch override: $BRANCH"
else
echo "No Legate.jl branch override — using JLL"
fi

- name: Clone and develop Legate.jl branch override
if: steps.legate-branch.outputs.branch != ''
run: |
git clone --depth 1 --branch ${{ steps.legate-branch.outputs.branch }} \
${{ github.server_url }}/${{ github.repository_owner }}/Legate.jl.git \
/tmp/Legate.jl
julia --color=yes -e '
using Pkg;
Pkg.develop(PackageSpec(path = "/tmp/Legate.jl/lib/LegatePreferences"))
Pkg.develop(PackageSpec(path = "/tmp/Legate.jl"))
using LegatePreferences; LegatePreferences.use_developer_mode();
Pkg.build("Legate")
'

- name: Setup cuNumeric.jl with build from src wrappers
run: |
julia --color=yes -e '
Expand All @@ -95,6 +129,11 @@ jobs:
using CNPreferences; CNPreferences.use_developer_mode();
Pkg.develop(PackageSpec(path = "."))
'
julia --color=yes --project=test -e '
using Pkg;
Pkg.develop(PackageSpec(path = "lib/CNPreferences"))
using CNPreferences; CNPreferences.use_developer_mode();
'
julia --color=yes -e 'using Pkg; Pkg.build("cuNumeric")'

- name: Perform Test
Expand Down
Loading
Loading