-
Notifications
You must be signed in to change notification settings - Fork 116
Workflow for executing examples #445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
oleksandr-pavlyk
merged 28 commits into
NVIDIA:main
from
oleksandr-pavlyk:workflow-for-executing-examples
Aug 4, 2026
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
a5cf623
Correct signature for .allocate method, stream is kwarg only now
oleksandr-pavlyk 8070510
Add example of using state.set_stream
oleksandr-pavlyk f6130e0
Add script to run examples by groups
oleksandr-pavlyk 42369d0
Update axes.py example to use .allocate with kwarg
oleksandr-pavlyk 70c590b
Break single requirements.txt into multiple files per example group
oleksandr-pavlyk f8bde85
Add workflow to execute Python examples in GPU runner
oleksandr-pavlyk ddc165f
Include stream.py in run_python_examples.py script
oleksandr-pavlyk bf40e53
Constrain Python example CI dependencies
oleksandr-pavlyk 4b48fe5
Scheduled jobs should include heavier examples
oleksandr-pavlyk 2ed6410
Grant lowest required permission in the workflow
oleksandr-pavlyk 53274b9
Expand the comment to explain what use of set_stream accomplishes
oleksandr-pavlyk cd45304
Add a comment stating the objective of example-running step in workflow
oleksandr-pavlyk e1f1e66
Specify 60 minutes timeout for the workflow step to run examples
oleksandr-pavlyk c282659
wrap subprocess.run calls in try/except
oleksandr-pavlyk decbc43
Handle FileNotFoundError in run_python_examples.py
oleksandr-pavlyk cf4ae90
Make sure to fail if compiler is not installed
oleksandr-pavlyk 49864d2
Propagate non-zero return status from running examples
oleksandr-pavlyk 89fe314
Address Ruff lint
oleksandr-pavlyk 477776e
Refactor common logic of outputting RUN/PASS/FAIL for examples
oleksandr-pavlyk b36036e
Refactor duplicated logic
oleksandr-pavlyk f1738c3
Make sure we search for wheel corresponding to ABI tag of the Python …
oleksandr-pavlyk dcf282c
Catch all exceptions around find_spec
oleksandr-pavlyk 708fa77
Execute run_example_env in subshell to isolate it
oleksandr-pavlyk 9b63513
Reduce time constraints on running example groups to fit 60 minutes
oleksandr-pavlyk e4000d6
Echo ::endgroup:: unconditionally, error or normal run
oleksandr-pavlyk 69f33a4
No need to carry Python test file for CI script
oleksandr-pavlyk 886df7c
Address feedback
oleksandr-pavlyk f1a7097
Use command -v, instead of which
oleksandr-pavlyk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| #!/usr/bin/env python3 | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION. | ||
| # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import re | ||
| from pathlib import Path | ||
|
|
||
| PIN_RE = re.compile(r"^\s*([A-Za-z0-9_.-]+)\s*==\s*(\S+)\s*$") | ||
| DIRECT_REF_RE = re.compile(r"^\s*([A-Za-z0-9_.-]+)\s*@\s*(\S.*)$") | ||
| REQ_NAME_RE = re.compile(r"^\s*([A-Za-z0-9_.-]+)") | ||
|
|
||
|
|
||
| def normalize_name(name: str) -> str: | ||
| return re.sub(r"[-_.]+", "-", name).lower() | ||
|
|
||
|
|
||
| def parse_requirement_include(stripped_line: str) -> str | None: | ||
| if stripped_line.startswith(("-r ", "--requirement ")): | ||
| _, include_path = stripped_line.split(maxsplit=1) | ||
| return include_path | ||
| if stripped_line.startswith("--requirement="): | ||
| return stripped_line.split("=", 1)[1].strip() | ||
| if stripped_line.startswith("-r") and len(stripped_line) > 2: | ||
| return stripped_line[2:].strip() | ||
| return None | ||
|
|
||
|
|
||
| def iter_requirement_lines(path: Path, seen: set[Path] | None = None): | ||
| if seen is None: | ||
| seen = set() | ||
| path = path.resolve() | ||
| if path in seen: | ||
| return | ||
| seen.add(path) | ||
|
|
||
| for raw_line in path.read_text(encoding="utf-8").splitlines(): | ||
| stripped = raw_line.split("#", 1)[0].strip() | ||
| if not stripped: | ||
| continue | ||
| if include_path := parse_requirement_include(stripped): | ||
| yield from iter_requirement_lines(path.parent / include_path, seen) | ||
| continue | ||
| if stripped.startswith("-"): | ||
| continue | ||
| yield stripped | ||
|
|
||
|
|
||
| def parse_constraint_names(path: Path) -> dict[str, str]: | ||
| names: dict[str, str] = {} | ||
| for stripped in iter_requirement_lines(path): | ||
| match = REQ_NAME_RE.match(stripped) | ||
| if match: | ||
| original_name = match.group(1) | ||
| names[normalize_name(original_name)] = original_name | ||
| return names | ||
|
|
||
|
|
||
| def parse_freeze_file(path: Path) -> dict[str, str]: | ||
| pins: dict[str, str] = {} | ||
| for line in path.read_text(encoding="utf-8").splitlines(): | ||
| match = PIN_RE.match(line) | ||
| if match: | ||
| pins[normalize_name(match.group(1))] = f"{match.group(1)}=={match.group(2)}" | ||
| continue | ||
| match = DIRECT_REF_RE.match(line) | ||
| if match: | ||
| package_name = match.group(1) | ||
| pins[normalize_name(package_name)] = ( | ||
| f"# DIRECT {package_name}: {line.strip()}" | ||
| ) | ||
| return pins | ||
|
|
||
|
|
||
| def parse_requirement_names(path: Path) -> set[str]: | ||
| names: set[str] = set() | ||
| for stripped in iter_requirement_lines(path): | ||
| req = stripped.split(";", 1)[0].strip() | ||
| req = req.split("[", 1)[0].strip() | ||
| match = REQ_NAME_RE.match(req) | ||
| if match: | ||
| names.add(normalize_name(match.group(1))) | ||
| return names | ||
|
|
||
|
|
||
| def collect_freeze_pins(freeze_dir: Path) -> dict[str, dict[str, set[str]]]: | ||
| package_to_envs: dict[str, dict[str, set[str]]] = {} | ||
| for freeze_file in sorted(freeze_dir.glob("*.txt")): | ||
| env_name = freeze_file.stem | ||
| for package_name, pin in parse_freeze_file(freeze_file).items(): | ||
| envs = package_to_envs.setdefault(package_name, {}) | ||
| envs.setdefault(pin, set()).add(env_name) | ||
| return package_to_envs | ||
|
|
||
|
|
||
| def print_constraints_candidate( | ||
| *, | ||
| constraints_file: Path, | ||
| freeze_dir: Path, | ||
| python_version: str, | ||
| cuda_extra: str, | ||
| ) -> bool: | ||
| tracked_names = parse_constraint_names(constraints_file) | ||
| package_to_envs = collect_freeze_pins(freeze_dir) | ||
|
|
||
| print("::group::Constraints candidate for tracked Python example packages") | ||
| print(f"# Source: {constraints_file}") | ||
| print("# Generated from floating example dependency run") | ||
| print(f"# Python: {python_version}") | ||
| print(f"# CUDA extra: {cuda_extra}") | ||
|
|
||
| conflicts: list[str] = [] | ||
| missing: list[str] = [] | ||
| for normalized_name in sorted(tracked_names): | ||
| pins = package_to_envs.get(normalized_name) | ||
| if not pins: | ||
| missing.append(tracked_names[normalized_name]) | ||
| continue | ||
| if len(pins) > 1: | ||
| detail = ", ".join( | ||
| f"{pin} ({', '.join(sorted(envs))})" | ||
| for pin, envs in sorted(pins.items()) | ||
| ) | ||
| conflicts.append(f"# CONFLICT {tracked_names[normalized_name]}: {detail}") | ||
| continue | ||
| print(next(iter(pins))) | ||
|
|
||
| for line in conflicts: | ||
| print(line) | ||
| for package_name in missing: | ||
| print(f"# MISSING {package_name}") | ||
| print("::endgroup::") | ||
| return not conflicts and not missing | ||
|
|
||
|
|
||
| def print_untracked_direct_requirements( | ||
| *, requirements_file: Path, constraints_file: Path | ||
| ) -> None: | ||
| tracked_names = set(parse_constraint_names(constraints_file)) | ||
| direct_names = parse_requirement_names(requirements_file) | ||
| untracked_names = sorted(direct_names - tracked_names) | ||
|
|
||
| print("::group::Untracked direct Python example requirements") | ||
| print(f"# Requirements source: {requirements_file}") | ||
| print(f"# Constraints source: {constraints_file}") | ||
| if untracked_names: | ||
| for name in untracked_names: | ||
| print(name) | ||
| else: | ||
| print("# None") | ||
| print("::endgroup::") | ||
|
|
||
|
|
||
| def parse_args() -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser( | ||
| description="Format floating Python example dependency results for CI logs." | ||
| ) | ||
| parser.add_argument("--constraints-file", type=Path, required=True) | ||
| parser.add_argument("--requirements-file", type=Path, required=True) | ||
| parser.add_argument("--freeze-dir", type=Path, required=True) | ||
| parser.add_argument("--python-version", required=True) | ||
| parser.add_argument("--cuda-extra", required=True) | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def main() -> int: | ||
| args = parse_args() | ||
| constraints_ok = print_constraints_candidate( | ||
| constraints_file=args.constraints_file, | ||
| freeze_dir=args.freeze_dir, | ||
| python_version=args.python_version, | ||
| cuda_extra=args.cuda_extra, | ||
| ) | ||
| print_untracked_direct_requirements( | ||
| requirements_file=args.requirements_file, | ||
| constraints_file=args.constraints_file, | ||
| ) | ||
| return 0 if constraints_ok else 1 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.