Skip to content

Commit bd5833f

Browse files
committed
ci: use docker executors with conditional jobs and best practices
1 parent 329ff38 commit bd5833f

5 files changed

Lines changed: 267 additions & 40 deletions

File tree

.github/workflows/ci.yml

Lines changed: 98 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,61 +16,124 @@ concurrency:
1616
cancel-in-progress: true
1717

1818
jobs:
19+
check-changes:
20+
name: Check for source changes
21+
runs-on: ubuntu-latest
22+
outputs:
23+
source-changed: ${{ steps.changes.outputs.source == 'true' }}
24+
docs-changed: ${{ steps.changes.outputs.docs == 'true' }}
25+
steps:
26+
- name: Checkout repository
27+
uses: actions/checkout@v6
28+
with:
29+
fetch-depth: 0
30+
31+
- name: Check for changes
32+
id: changes
33+
uses: dorny/paths-filter@v3
34+
with:
35+
filters: |
36+
source:
37+
- 'src/**'
38+
- 'cext/**'
39+
- 'test/**'
40+
- 'samples/**'
41+
- 'pyproject.toml'
42+
- 'setup.py'
43+
- 'CMakeLists.txt'
44+
- 'ci/**'
45+
- '.github/workflows/**'
46+
- 'Dockerfile.ci'
47+
docs:
48+
- 'docs/**'
49+
- '*.md'
50+
51+
build-ci-image:
52+
name: Build CI Image
53+
runs-on: ubuntu-latest
54+
timeout-minutes: 30
55+
needs: check-changes
56+
if: needs.check-changes.outputs.source-changed == 'true'
57+
steps:
58+
- name: Checkout repository
59+
uses: actions/checkout@v6
60+
61+
- name: Set up Docker Buildx
62+
uses: docker/setup-buildx-action@v3
63+
with:
64+
buildkitd-config: /etc/buildkit/buildkitd.toml
65+
66+
- name: Build CI image
67+
timeout-minutes: 20
68+
uses: docker/build-push-action@v6
69+
with:
70+
context: .
71+
file: ./Dockerfile.ci
72+
tags: cutile-ci:${{ github.sha }}
73+
cache-from: type=gha
74+
cache-to: type=gha,mode=max
75+
load: true
76+
outputs: type=docker,compression=zstd
77+
78+
- name: Export CI image
79+
run: |
80+
docker save cutile-ci:${{ github.sha }} | gzip > cutile-ci-image.tar.gz
81+
82+
- name: Upload CI image artifact
83+
uses: actions/upload-artifact@v5
84+
with:
85+
name: ci-image
86+
path: cutile-ci-image.tar.gz
87+
retention-days: 1
1988
lint:
2089
name: Lint
2190
runs-on: ubuntu-latest
2291
timeout-minutes: 10
92+
container:
93+
image: python:3.10-slim
2394
steps:
2495
- name: Checkout repository
2596
uses: actions/checkout@v6
2697

27-
- name: Set up Python
28-
uses: actions/setup-python@v6
29-
with:
30-
python-version: "3.10"
31-
3298
- name: Install lint dependencies
33-
run: |
34-
python -m pip install --upgrade pip
35-
pip install flake8 reuse
99+
run: pip install --no-cache-dir flake8==7.3.0 reuse==5.1.0
36100

37101
- name: Run flake8
38102
run: flake8
39103

104+
- name: Run cpplint
105+
run: python ci/cpplint.py
106+
40107
- name: Check license headers (REUSE)
41-
run: |
42-
outputs=$(reuse lint --lines | grep -v -e "src/cuda/tile/VERSION" || true)
43-
if [ -n "$outputs" ]; then
44-
echo -e "License check failed\n${outputs}"
45-
exit 1
46-
fi
108+
run: ci/scripts/check_license.sh
47109

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

51113
build-wheel:
52114
name: Build wheel (Linux x86_64, Python ${{ matrix.python-version }})
53115
runs-on: ubuntu-latest
116+
needs: [check-changes, build-ci-image]
117+
if: needs.check-changes.outputs.source-changed == 'true'
54118
timeout-minutes: 30
55119
container:
56-
image: nvidia/cuda:13.0.2-devel-ubuntu24.04
120+
image: cutile-ci:${{ github.sha }}
57121
strategy:
58122
fail-fast: false
59123
matrix:
60124
python-version: ["3.10", "3.11", "3.12", "3.13"]
61125
steps:
126+
- name: Load CI image
127+
uses: actions/download-artifact@v5
128+
with:
129+
name: ci-image
130+
- run: docker load < cutile-ci-image.tar.gz
131+
62132
- name: Checkout repository
63133
uses: actions/checkout@v6
64134

65-
- name: Install build dependencies
66-
run: |
67-
apt-get update
68-
apt-get install -y software-properties-common cmake build-essential git
69-
add-apt-repository -y ppa:deadsnakes/ppa
70-
apt-get update
71-
apt-get install -y python${{ matrix.python-version }} python${{ matrix.python-version }}-venv python${{ matrix.python-version }}-dev
72-
73135
- name: Build wheel
136+
timeout-minutes: 15
74137
run: |
75138
python${{ matrix.python-version }} -m venv .venv
76139
.venv/bin/pip install --upgrade pip build wheel setuptools
@@ -86,17 +149,21 @@ jobs:
86149
build-docs:
87150
name: Build docs
88151
runs-on: ubuntu-latest
152+
needs: [check-changes, build-ci-image, build-wheel]
153+
if: always() && (needs.check-changes.outputs.source-changed == 'true' || needs.check-changes.outputs.docs-changed == 'true')
89154
timeout-minutes: 15
90-
needs: build-wheel
155+
container:
156+
image: cutile-ci:${{ github.sha }}
91157
steps:
158+
- name: Load CI image
159+
uses: actions/download-artifact@v5
160+
with:
161+
name: ci-image
162+
- run: docker load < cutile-ci-image.tar.gz
163+
92164
- name: Checkout repository
93165
uses: actions/checkout@v6
94166

95-
- name: Set up Python
96-
uses: actions/setup-python@v6
97-
with:
98-
python-version: "3.10"
99-
100167
- name: Download wheel artifact
101168
uses: actions/download-artifact@v5
102169
with:
@@ -105,14 +172,13 @@ jobs:
105172

106173
- name: Install dependencies
107174
run: |
108-
python -m pip install --upgrade pip
109175
pip install dist/*.whl
110-
pip install -r docs/requirements.txt
111176
112177
- name: Build documentation
178+
timeout-minutes: 10
113179
run: |
114180
cd docs
115-
make html
181+
timeout 8m make html
116182
117183
- name: Upload documentation artifact
118184
uses: actions/upload-artifact@v5

Dockerfile.ci

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# SPDX-FileCopyrightText: Copyright (c) <2025> NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# CI stage
6+
FROM nvidia/cuda:13.0.2-devel-ubuntu24.04 AS ci
7+
8+
ARG PYTHON_VERSION=3.10
9+
ENV PYTHON_VERSION=${PYTHON_VERSION}
10+
ENV DEBIAN_FRONTEND=noninteractive
11+
12+
RUN apt-get update && apt-get install -y --no-install-recommends \
13+
git \
14+
wget \
15+
cmake \
16+
build-essential \
17+
software-properties-common \
18+
&& rm -rf /var/lib/apt/lists/*
19+
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
20+
apt-get update && \
21+
apt-get install -y \
22+
python3.10 python3.10-venv python3.10-dev \
23+
python3.11 python3.11-venv python3.11-dev \
24+
python3.12 python3.12-venv python3.12-dev \
25+
python3.13 python3.13-venv python3.13-dev \
26+
python3-pip \
27+
&& rm -rf /var/lib/apt/lists/*
28+
29+
COPY test/requirements.txt /tmp/requirements-test.txt
30+
COPY docs/requirements.txt /tmp/requirements-docs.txt
31+
32+
RUN pip install --no-cache-dir setuptools wheel build
33+
RUN pip install --no-cache-dir \
34+
-r /tmp/requirements-test.txt \
35+
-r /tmp/requirements-docs.txt
36+
ENV CUDA_HOME="/usr/local/cuda"
37+
ENV PATH="$PATH:/usr/local/cuda/bin"
38+
ENV LD_LIBRARY_PATH="/usr/local/cuda/lib64:${LD_LIBRARY_PATH}"
39+
40+
WORKDIR /workspace

ci/cpplint.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env python3
2+
# SPDX-FileCopyrightText: Copyright (c) <2025> NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
import os
7+
import sys
8+
9+
file_extensions = [
10+
".h",
11+
".hpp",
12+
".hh",
13+
".c",
14+
".C",
15+
".cpp",
16+
".cxx",
17+
".cc",
18+
".pyx",
19+
".pxd",
20+
]
21+
max_line_len = 100
22+
23+
24+
def should_lint(filename: str):
25+
return any(filename.endswith(x) for x in file_extensions)
26+
27+
28+
def lint(paths):
29+
num_errors = 0
30+
num_files = 0
31+
32+
def report_error(message: str):
33+
nonlocal num_errors
34+
print(f"{full_name[len(path) + 1:]}:{i + 1}: {message}", file=sys.stderr)
35+
num_errors += 1
36+
37+
for path in paths:
38+
for root, dirs, files in os.walk(path):
39+
for filename in files:
40+
if not should_lint(filename):
41+
continue
42+
full_name = os.path.join(root, filename)
43+
with open(full_name, "r") as f:
44+
for i, line in enumerate(f):
45+
if "noqa" in line:
46+
continue
47+
if "SPDX" in line:
48+
continue
49+
50+
length = len(line)
51+
if line.endswith("\n"):
52+
length -= 1
53+
if length > max_line_len:
54+
report_error(
55+
f"Line is longer than {max_line_len} characters"
56+
)
57+
if length > 0 and line[length - 1].isspace():
58+
report_error("Trailing whitespace at the end of the line")
59+
num_files += 1
60+
61+
if num_errors > 0:
62+
print(f"Found {num_errors} errors", file=sys.stderr)
63+
sys.exit(1)
64+
elif num_files == 0:
65+
print("No input files found!", file=sys.stderr)
66+
sys.exit(2)
67+
else:
68+
print(f"Checked {num_files} files, all OK")
69+
70+
71+
if __name__ == "__main__":
72+
script_dir = os.path.dirname(__file__)
73+
project_root = os.getcwd()
74+
dirs = ["cext", "src"]
75+
paths = [os.path.join(project_root, d) for d in dirs]
76+
lint(paths)

ci/scripts/check_license.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
# SPDX-FileCopyrightText: Copyright (c) <2025> NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
ignore_files=("src/cuda/tile/VERSION")
7+
outputs=$(reuse lint --lines | grep -v ${ignore_files[@]/#/-e })
8+
if [ -n "$outputs" ]; then
9+
echo -e "License check failed\n${outputs}"
10+
exit 1
11+
fi

docs/source/conf.py

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
import os
6-
import cuda.tile
7-
import cuda.tile._datatype
6+
7+
# Try to import CUDA modules, but handle the case where CUDA is not available
8+
try:
9+
import cuda.tile
10+
import cuda.tile._datatype
11+
CUDA_AVAILABLE = True
12+
except (ImportError, OSError) as e:
13+
# CUDA libraries are not available (e.g., in CI environment)
14+
CUDA_AVAILABLE = False
15+
print(f"Warning: CUDA not available, documentation will be built without dynamic content: {e}")
816

917
# Configuration file for the Sphinx documentation builder.
1018
#
@@ -39,6 +47,10 @@
3947
autodoc_typehints = 'none'
4048
toc_object_entries = False # Don't include object entries in the TOC
4149

50+
# Handle missing modules gracefully for autodoc
51+
if not CUDA_AVAILABLE:
52+
autodoc_mock_imports = ['cuda.tile._cext', 'cuda.tile._load_libcuda']
53+
4254
# -- Options for HTML output -------------------------------------------------
4355
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
4456
html_theme = "nvidia_sphinx_theme"
@@ -68,13 +80,35 @@
6880
"dtype_promotion_table",
6981
"numeric_dtypes"
7082
]
83+
7184
# Generate RST include files
72-
for include_name in rst_generated_includes:
73-
function_name = f"_generate_rst_{include_name}"
74-
file_path = os.path.join(generated_includes_dir, f"{include_name}.rst")
75-
content = getattr(cuda.tile._datatype, function_name)()
76-
with open(file_path, 'w') as f:
77-
f.write(content)
85+
if CUDA_AVAILABLE:
86+
# Generate dynamic content when CUDA is available
87+
for include_name in rst_generated_includes:
88+
function_name = f"_generate_rst_{include_name}"
89+
file_path = os.path.join(generated_includes_dir, f"{include_name}.rst")
90+
content = getattr(cuda.tile._datatype, function_name)()
91+
with open(file_path, 'w') as f:
92+
f.write(content)
93+
else:
94+
# Generate fallback content when CUDA is not available
95+
fallback_content = {
96+
"dtype_promotion_table": """.. note::
97+
CUDA is not available in this environment. The dtype promotion table could not be generated.
98+
99+
Please refer to the source documentation or build in an environment with CUDA support for the complete table.""",
100+
101+
"numeric_dtypes": """.. note::
102+
CUDA is not available in this environment. The numeric dtypes documentation could not be generated.
103+
104+
Please refer to the source documentation or build in an environment with CUDA support for the complete list."""
105+
}
106+
107+
for include_name in rst_generated_includes:
108+
file_path = os.path.join(generated_includes_dir, f"{include_name}.rst")
109+
content = fallback_content.get(include_name, f"Content for {include_name} not available without CUDA.")
110+
with open(file_path, 'w') as f:
111+
f.write(content)
78112

79113
# Include substitutions from references.rst in all documents (including docstrings)
80114
with open(os.path.join(os.path.dirname(__file__), 'references.rst'), 'r') as f:

0 commit comments

Comments
 (0)