Skip to content
Open
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
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. cuNumeric.jl), change only this block.

PACKAGE_NAME = "Legate"

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

SUBPKG_TOML_PATH = "lib/LegatePreferences/Project.toml"
SUBPKG_COMPAT_KEY = "LegatePreferences"
SUBPKG_SRC_PREFIXES = (
"lib/LegatePreferences/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()
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
- 'src/**'
- 'scripts/**'
- 'deps/build.jl'
- 'deps/buildtools/**'
- 'Project.toml'
- 'lib/LegatePreferences/src/**'
- '.github/workflows/ci.yml'
Expand All @@ -29,13 +30,30 @@ on:
- 'src/**'
- 'scripts/**'
- 'deps/build.jl'
- 'deps/buildtools/**'
- 'Project.toml'
- 'lib/LegatePreferences/src/**'
- '.github/workflows/ci.yml'
- 'Dockerfile' # container.yml depends on this
jobs:
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/legate_jl_wrapper/**'

test:
name: Julia ${{ matrix.julia }} - ${{ matrix.os }}
needs: check_changes
if: ${{ github.base_ref == 'main' || needs.check_changes.outputs.wrapper_changed != 'true' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/developer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ on:
- 'src/**'
- 'scripts/**'
- 'deps/build.jl'
- 'deps/buildtools/**'
- 'Project.toml'
- 'lib/legate_jl_wrapper/src/**'
- 'lib/legate_jl_wrapper/include/**'
Expand All @@ -32,14 +33,19 @@ on:
- 'src/**'
- 'scripts/**'
- 'deps/build.jl'
- 'deps/buildtools/**'
- 'Project.toml'
- 'lib/legate_jl_wrapper/src/**'
- 'lib/legate_jl_wrapper/include/**'
- 'lib/LegatePreferences/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 +61,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
16 changes: 16 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,24 @@ on:
- 'README.md'
- 'lib/LegatePreferences/src*'
jobs:
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/legate_jl_wrapper/**'

docs:
name : Documentation
needs: check_changes
if: ${{ github.base_ref == 'main' || needs.check_changes.outputs.wrapper_changed != 'true' }}
permissions:
actions: write
contents: write
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/pkg_resolve.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Pkg Resolve

on:
workflow_call:

jobs:
resolve:
name: Pkg.resolve
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: julia-actions/setup-julia@v2
with:
version: '1.10'

- uses: julia-actions/cache@v2

- name: Pkg.resolve
run: |
rm -f Manifest.toml
julia --project -e 'using Pkg; Pkg.Registry.add("General"); Pkg.resolve()'
25 changes: 25 additions & 0 deletions .github/workflows/version_check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Version Check

on:
pull_request:
branches:
- main

jobs:
version-check:
name: Version Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Fetch main branch
run: git fetch origin main

- uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Run version checks
run: python .github/scripts/check_versions.py
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ LocalPreferences.toml
# Files generated by the build process
libcxxwrap-julia*
build*
!deps/buildtools/
!deps/buildtools/**
wrapper/build

legate_wrapper_install
Expand Down Expand Up @@ -55,5 +57,8 @@ docs/site/
# environment.
Manifest.toml

# Generated by build.jl — do not commit
dev/Project.toml
dev/Manifest.toml

test_crash.sh
Loading
Loading