Skip to content
Open
86 changes: 82 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,26 @@ concurrency:
cancel-in-progress: true

jobs:
images:
name: Define Base Images
runs-on: ubuntu-latest
outputs:
lint: ghcr.io/nvidia/cutile-python/lint:2026-01-08-38b50c94b44d
docs: ghcr.io/nvidia/cutile-python/docs:2026-01-09-e5bcaad3d697
build_py310: ghcr.io/nvidia/cutile-python/build_py_3.10_x86_64:2026-01-08-3798e502569b
build_py311: ghcr.io/nvidia/cutile-python/build_py_3.11_x86_64:2026-01-08-f3b483c26202
build_py312: ghcr.io/nvidia/cutile-python/build_py_3.12_x86_64:2026-01-08-eb3493bdf2cc
build_py313: ghcr.io/nvidia/cutile-python/build_py_3.13_x86_64:2026-01-08-9ac1fd38fa6f
steps:
- run: echo "Defining image tags"

lint:
name: Lint
needs: images
runs-on: ubuntu-latest
timeout-minutes: 10
container:
image: ghcr.io/nvidia/cutile-python/lint:2025-12-06-4cb7d16e4c20
image: ${{ needs.images.outputs.lint }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
Expand All @@ -30,10 +44,74 @@ jobs:
run: flake8

- name: Run cpplint
run: python3 ci/cpplint.py
run: python scripts/cpplint.py

- name: Check license headers (REUSE)
run: ci/scripts/check_license.sh
run: scripts/check_license.sh

- name: Check inline samples are up to date
run: python3 test/tools/inline_samples.py --check
run: python test/tools/inline_samples.py --check

docs:
name: Build Docs
if: false # Waiting for CUDA lazy-loading fix to land on main
needs: [images, build]
runs-on: ubuntu-latest
timeout-minutes: 10
container:
image: ${{ needs.images.outputs.docs }}
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Download wheel
uses: actions/download-artifact@v4
with:
name: wheel-py3.12-linux-x86_64
path: dist/

- name: Install wheel
run: pip install dist/*.whl

- name: Build documentation
run: make -C docs html

- name: Upload docs artifact
uses: actions/upload-artifact@v4
with:
name: docs-html
path: docs/build/html
retention-days: 7

build:
name: Build Wheel (Python ${{ matrix.python-version }})
needs: images
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
include:
- python-version: "3.10"
image_key: build_py310
- python-version: "3.11"
image_key: build_py311
- python-version: "3.12"
image_key: build_py312
- python-version: "3.13"
image_key: build_py313
container:
image: ${{ needs.images.outputs[matrix.image_key] }}
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Build wheel
run: python setup.py bdist_wheel

- name: Upload wheel artifact
uses: actions/upload-artifact@v4
with:
name: wheel-py${{ matrix.python-version }}-linux-x86_64
path: dist/*.whl
if-no-files-found: error
retention-days: 7
12 changes: 12 additions & 0 deletions scripts/check_license.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (c) <2025> NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

ignore_files=("src/cuda/tile/VERSION")
outputs=$(reuse lint --lines | grep -v ${ignore_files[@]/#/-e })
if [ -n "$outputs" ]; then
echo -e "License check failed\n${outputs}"
exit 1
fi

76 changes: 76 additions & 0 deletions scripts/cpplint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) <2025> NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

import os
import sys

file_extensions = [
".h",
".hpp",
".hh",
".c",
".C",
".cpp",
".cxx",
".cc",
".pyx",
".pxd",
]
max_line_len = 100


def should_lint(filename: str):
return any(filename.endswith(x) for x in file_extensions)


def lint(paths):
num_errors = 0
num_files = 0

def report_error(message: str):
nonlocal num_errors
print(f"{full_name[len(path) + 1:]}:{i + 1}: {message}", file=sys.stderr)
num_errors += 1

for path in paths:
for root, dirs, files in os.walk(path):
for filename in files:
if not should_lint(filename):
continue
full_name = os.path.join(root, filename)
with open(full_name, "r") as f:
for i, line in enumerate(f):
if "noqa" in line:
continue
if "SPDX" in line:
continue

length = len(line)
if line.endswith("\n"):
length -= 1
if length > max_line_len:
report_error(
f"Line is longer than {max_line_len} characters"
)
if length > 0 and line[length - 1].isspace():
report_error("Trailing whitespace at the end of the line")
num_files += 1

if num_errors > 0:
print(f"Found {num_errors} errors", file=sys.stderr)
sys.exit(1)
elif num_files == 0:
print("No input files found!", file=sys.stderr)
sys.exit(2)
else:
print(f"Checked {num_files} files, all OK")


if __name__ == "__main__":
script_dir = os.path.dirname(__file__)
project_root = os.getcwd()
dirs = ["cext", "torch_cext", "src"]
paths = [os.path.join(project_root, d) for d in dirs]
lint(paths)