Skip to content

Commit 88928c1

Browse files
committed
Implement initial Windows CI support for the Python cuda-cccl library.
1 parent 1e6a6f5 commit 88928c1

File tree

13 files changed

+888
-9
lines changed

13 files changed

+888
-9
lines changed

.github/actions/workflow-run-job-windows/action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ runs:
188188
docker_exit=0
189189
set +e
190190
docker run \
191+
-v //./pipe/docker_engine://./pipe/docker_engine \
191192
--mount type=bind,source="${{steps.paths.outputs.HOST_REPO}}",target="${{steps.paths.outputs.MOUNT_REPO}}" \
192193
--mount type=bind,source="${{ env.ARTIFACT_UPLOAD_STAGE_WIN }}",target="${{ env.ARTIFACT_UPLOAD_STAGE_WIN }}" \
193194
--mount type=bind,source="${{ env.ARTIFACT_ARCHIVES_WIN }}",target="${{ env.ARTIFACT_ARCHIVES_WIN }}" \
@@ -197,14 +198,18 @@ runs:
197198
--isolation=process \
198199
${{ env.ENABLE_GPU }} \
199200
--env COMMAND='& ${{inputs.command}}' \
201+
--env COMMAND='& ${{inputs.command}}' \
200202
--env "ARTIFACT_ARCHIVES=${{env.ARTIFACT_ARCHIVES}}" \
201203
--env "ARTIFACT_UPLOAD_REGISTERY=${{env.ARTIFACT_UPLOAD_REGISTERY}}" \
202204
--env "ARTIFACT_UPLOAD_STAGE=${{env.ARTIFACT_UPLOAD_STAGE}}" \
203205
--env "AWS_ACCESS_KEY_ID=${{env.AWS_ACCESS_KEY_ID}}" \
204206
--env "AWS_SECRET_ACCESS_KEY=${{env.AWS_SECRET_ACCESS_KEY}}" \
205207
--env "AWS_SESSION_TOKEN=${{env.AWS_SESSION_TOKEN}}" \
208+
--env "CONTAINER_WORKSPACE=${{steps.paths.outputs.MOUNT_REPO}}" \
206209
--env "CI=true" \
207210
--env "DEVCONTAINER_NAME=cuda${{inputs.cuda}}-${{inputs.host}}" \
211+
--env "DOCKER_HOST=npipe:////./pipe/docker_engine" \
212+
--env "HOST_WORKSPACE=${{steps.paths.outputs.HOST_REPO}}" \
208213
--env "GH_TOKEN=$GH_TOKEN" \
209214
--env "GITHUB_ACTIONS=$GITHUB_ACTIONS" \
210215
--env "GITHUB_REF_NAME=$GITHUB_REF_NAME" \

c/parallel/src/three_way_partition.cu

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,12 @@ std::string get_three_way_partition_kernel_name(
9999
std::string_view select_second_part_op_name)
100100
{
101101
std::string chained_policy_t;
102-
check(nvrtcGetTypeName<device_three_way_partition_policy>(&chained_policy_t));
102+
check(cccl_type_name_from_nvrtc<device_three_way_partition_policy>(&chained_policy_t));
103103

104104
constexpr std::string_view scan_tile_state_t = "cub::detail::three_way_partition::ScanTileStateT";
105105

106106
std::string offset_t;
107-
check(nvrtcGetTypeName<OffsetT>(&offset_t));
107+
check(cccl_type_name_from_nvrtc<OffsetT>(&offset_t));
108108

109109
const std::string streaming_context_t =
110110
std::format("cub::detail::three_way_partition::streaming_context_t<{0}>", offset_t);

ci/matrix.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ projects:
501501
name: "Python"
502502
job_map:
503503
build: ['build_py_wheel']
504-
test: ['test_py_headers', 'test_py_coop', 'test_py_par', test_py_examples]
504+
test: ['test_py_headers', 'test_py_coop', 'test_py_par', 'test_py_examples']
505505
cccl_c_parallel:
506506
name: 'CCCL C Parallel'
507507
stds: [20]

ci/windows/build_common.psm1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ $ErrorActionPreference = "Stop"
1919
$script:HOST_COMPILER = (Get-Command "cl").source -replace '\\','/'
2020
$script:PARALLEL_LEVEL = $env:NUMBER_OF_PROCESSORS
2121

22+
Write-Host "=== Docker Container Resource Info ==="
23+
Write-Host "Number of Processors: $script:PARALLEL_LEVEL"
24+
Get-WmiObject Win32_OperatingSystem | ForEach-Object {
25+
Write-Host ("Memory: total={0:N1} GB, free={1:N1} GB" -f ($_.TotalVisibleMemorySize / 1MB), ($_.FreePhysicalMemory / 1MB))
26+
}
27+
Write-Host "======================================"
28+
2229
# Extract the CL version for export to build scripts:
2330
$script:CL_VERSION_STRING = & cl.exe /?
2431
if ($script:CL_VERSION_STRING -match "Version (\d+\.\d+)\.\d+") {
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
function Get-LatestPythonPatchVersionFromPyEnvWin {
2+
<#
3+
.SYNOPSIS
4+
Resolves the latest patch version for a given Python major.minor (e.g.
5+
'3.12') by parsing `pyenv install --list` output on Windows (pyenv-win).
6+
.PARAMETER Version
7+
A string in the form 'M.m' (e.g., '3.10', '3.11', '3.12').
8+
#>
9+
[CmdletBinding()]
10+
param(
11+
[Parameter(Mandatory, Position = 0)]
12+
[ValidatePattern('^\d+\.\d+$')]
13+
[string]$Version
14+
)
15+
16+
# Verify pyenv exists.
17+
if (-not (Get-Command pyenv -ErrorAction SilentlyContinue)) {
18+
throw [System.InvalidOperationException]::new(
19+
'pyenv-win ("pyenv") not found on PATH.'
20+
)
21+
}
22+
23+
$listOutput = & pyenv install --list 2>&1
24+
if ($LASTEXITCODE -ne 0 -or -not $listOutput) {
25+
$joined = $listOutput -join "`n"
26+
throw [System.InvalidOperationException]::new(
27+
"Failed to run 'pyenv install --list'. Output:`n$joined"
28+
)
29+
}
30+
31+
# Build a list of patch numbers that match the requested minor version.
32+
$versionPrefix = "$Version."
33+
$patchNumbers = @()
34+
foreach ($line in $listOutput) {
35+
$candidate = $line.Trim()
36+
if (-not $candidate) { continue }
37+
38+
# Accept any major version; the StartsWith check guarantees we only
39+
# keep the wanted minor.
40+
if (-not $candidate.StartsWith($versionPrefix)) { continue }
41+
if ($candidate -notmatch '^\d+\.\d+\.\d+$') { continue }
42+
43+
$patchNumbers += [int]($candidate.Split('.')[2])
44+
}
45+
46+
if ($patchNumbers.Count -eq 0) {
47+
throw [System.InvalidOperationException]::new(
48+
"No installable CPython versions found for prefix " +
49+
"'$Version' in pyenv-win list."
50+
)
51+
}
52+
53+
$latestPatch = ($patchNumbers | Sort-Object -Descending)[0]
54+
return "$Version.$latestPatch"
55+
}
56+
57+
function Install-PythonViaPyEnvWin {
58+
<#
59+
.SYNOPSIS
60+
Ensures a Python version for the given major.minor exists via
61+
pyenv-win, activates it for the current shell, and returns the
62+
path to python.exe.
63+
.PARAMETER Version
64+
A string in the form 'M.m' (e.g., '3.12').
65+
#>
66+
param(
67+
[Parameter(Mandatory, Position = 0)]
68+
[ValidatePattern('^\d+\.\d+$')]
69+
[string]$Version
70+
)
71+
72+
$fullVersion = Get-LatestPythonPatchVersionFromPyEnvWin `
73+
-Version $Version
74+
75+
Write-Host "Installing Python $fullVersion via pyenv..."
76+
Write-Host "pyenv install $fullVersion"
77+
($null = & pyenv install $fullVersion | Out-Host)
78+
if ($LASTEXITCODE -ne 0) {
79+
throw [System.InvalidOperationException]::new(
80+
"Failed to install Python $fullVersion via pyenv."
81+
)
82+
}
83+
Write-Host "Successfully installed Python $fullVersion via pyenv."
84+
85+
($null = & pyenv local $fullVersion | Out-Host)
86+
if ($LASTEXITCODE -ne 0) {
87+
throw [System.InvalidOperationException]::new(
88+
"Failed to set Python $fullVersion as local via pyenv."
89+
)
90+
}
91+
Write-Host "Successfully set Python $fullVersion as local via pyenv."
92+
93+
# Avoid the shim (i.e. shims/python.bat) because it will attempt to set
94+
# a codepage via `chcp` that we probably won't have installed on our
95+
# Server Core-based image.
96+
$exe = (Resolve-Path -LiteralPath $(pyenv which python)).Path
97+
Write-Host "python.exe path: $exe"
98+
99+
# Add the root and Scripts directory to $Env:PATH.
100+
$rootDir = $exe.Replace("\python.exe", "")
101+
$scriptsDir = $exe.Replace("python.exe", "Scripts")
102+
$pathPrefix = $rootDir + ";" + $scriptsDir + ";"
103+
$Env:PATH = $pathPrefix + $Env:PATH
104+
105+
# Upgrade pip using the found exe. This is necessary because some older
106+
# versions of pip (e.g. 23.10) don't support arguments like `--wheeldir`.
107+
($null = & $exe -m pip install --upgrade pip --no-cache-dir | Out-Host)
108+
if ($LASTEXITCODE -ne 0) {
109+
throw [System.InvalidOperationException]::new("pip upgrade failed")
110+
}
111+
112+
Write-Host "pip successfully upgraded, running pyenv rehash..."
113+
($null = & pyenv rehash | Out-Host)
114+
if ($LASTEXITCODE -ne 0) {
115+
throw [System.InvalidOperationException]::new("pyenv rehash failed")
116+
}
117+
Write-Host "Successfully ran pyenv rehash."
118+
119+
return $exe
120+
}
121+
122+
function Get-Python {
123+
<#
124+
.SYNOPSIS
125+
Returns the path of the Python interpreter satisfying the supplied
126+
version, potentially installing it via pyenv-win if it's not already
127+
installed.
128+
#>
129+
[CmdletBinding()]
130+
param(
131+
[Parameter(Mandatory, Position = 0)]
132+
[ValidatePattern('^\d+\.\d+$')]
133+
[string]$Version
134+
)
135+
136+
# Look for a plain 'python.exe' already on the path.
137+
try {
138+
$candidate = (Get-Command python -ErrorAction Stop).Source
139+
$foundVer = & $candidate -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')" 2>$null
140+
if ($foundVer -eq $Version) {
141+
Write-Host "Found matching Python $foundVer at $candidate."
142+
return $candidate.Trim()
143+
}
144+
else {
145+
Write-Host "Found python.exe but version $foundVer != requested version $Version."
146+
}
147+
}
148+
catch {
149+
Write-Host "Unable to query existing 'python' on PATH: $_"
150+
}
151+
152+
# If we reach here, we'll need to install the requested version via pyenv.
153+
try {
154+
$exe = Install-PythonViaPyEnvWin -Version $Version
155+
return $exe.Trim()
156+
}
157+
catch {
158+
throw [System.InvalidOperationException]::new(
159+
"Requested Python $Version not found and installation " +
160+
"via pyenv-win failed: $($_.Exception.Message)"
161+
)
162+
}
163+
}
164+
165+
function Get-CudaMajor {
166+
<#
167+
.SYNOPSIS
168+
Gets the CUDA major version for this container instance (e.g. '12' or
169+
'13'). Defaults to '13' if no match can be found.
170+
#>
171+
if ($env:CUDA_PATH) {
172+
$nvcc = Join-Path $env:CUDA_PATH "bin/nvcc.exe"
173+
if (Test-Path $nvcc) {
174+
$out = & $nvcc --version 2>&1
175+
$text = ($out -join "`n")
176+
if ($text -match 'release\s+(\d+)\.') { return $Matches[1] }
177+
}
178+
# Fallback: parse major from CUDA_PATH like ...\v13.0 or ...\CUDA\13
179+
$pathMatch = [regex]::Match($env:CUDA_PATH, 'v?(\d+)(?:\.\d+)?')
180+
if ($pathMatch.Success) { return $pathMatch.Groups[1].Value }
181+
}
182+
return '13'
183+
}
184+
185+
function Convert-ToUnixPath {
186+
Param([Parameter(Mandatory = $true)][string]$p)
187+
return ($p -replace "\\", "/")
188+
}
189+
190+
function Get-CudaCcclWheel {
191+
<#
192+
.SYNOPSIS
193+
Returns the path of the cuda-cccl wheel artifact to use in the context
194+
of a GitHub Actions CI test script.
195+
#>
196+
Param()
197+
198+
$repoRoot = Get-RepoRoot
199+
if ($env:GITHUB_ACTIONS) {
200+
Push-Location $repoRoot
201+
try {
202+
$wheelArtifactName = (& bash -lc "ci/util/workflow/get_wheel_artifact_name.sh").Trim()
203+
if (-not $wheelArtifactName) { throw 'Failed to resolve wheel artifact name' }
204+
$repoRootPosix = Convert-ToUnixPath $repoRoot
205+
# Ensure output from downloader goes to console, not function return pipeline
206+
$null = (& bash -lc "ci/util/artifacts/download.sh $wheelArtifactName $repoRootPosix" 2>&1 | Out-Host)
207+
if ($LASTEXITCODE -ne 0) { throw "Failed to download wheel artifact '$wheelArtifactName'" }
208+
}
209+
finally { Pop-Location }
210+
}
211+
212+
$wheelhouse = Join-Path $repoRoot 'wheelhouse'
213+
$wheelPath = Get-OnePathMatch -Path $wheelhouse -Pattern '^cuda_cccl-.*\.whl' -File
214+
return $wheelPath
215+
}
216+
217+
function Get-OnePathMatch {
218+
<#
219+
.SYNOPSIS
220+
Returns a single path (file or directory) match for a given pattern,
221+
throwing an error if there were no matches or more than one match.
222+
#>
223+
[CmdletBinding(DefaultParameterSetName = 'FileSet')]
224+
param(
225+
[Parameter(Mandatory)]
226+
[string] $Path,
227+
228+
[Parameter(Mandatory)]
229+
[string] $Pattern,
230+
231+
[Parameter(Mandatory, ParameterSetName = 'FileSet')]
232+
[switch] $File,
233+
234+
[Parameter(Mandatory, ParameterSetName = 'DirSet')]
235+
[switch] $Directory,
236+
237+
[switch] $Recurse
238+
)
239+
240+
if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
241+
throw "Path not found or not a directory: $Path"
242+
}
243+
244+
$gciArgs = @{
245+
LiteralPath = $Path
246+
ErrorAction = 'SilentlyContinue'
247+
}
248+
249+
if ($Recurse) { $gciArgs['Recurse'] = $true }
250+
if ($PSCmdlet.ParameterSetName -eq 'FileSet') {
251+
$gciArgs['File'] = $true
252+
}
253+
else {
254+
$gciArgs['Directory'] = $true
255+
}
256+
257+
$pathMatches = @(
258+
Get-ChildItem @gciArgs |
259+
Where-Object { $_.Name -match $Pattern } |
260+
Select-Object -ExpandProperty FullName
261+
)
262+
263+
if ($pathMatches.Count -ne 1) {
264+
$kind = if ($PSCmdlet.ParameterSetName -eq 'FileSet') { 'file' }
265+
else { 'directory' }
266+
$indented = ($pathMatches | ForEach-Object { " $_" }) -join "`n"
267+
268+
$msg = @"
269+
Expected exactly one $kind name matching regex:
270+
$Pattern
271+
under:
272+
$Path
273+
Found:
274+
$($pathMatches.Count)
275+
276+
$indented
277+
"@
278+
throw $msg
279+
}
280+
281+
return $pathMatches[0]
282+
}
283+
284+
Export-ModuleMember -Function Get-Python, Get-CudaMajor, Convert-ToUnixPath, Get-RepoRoot, Get-CudaCcclWheel, Get-OnePathMatch

0 commit comments

Comments
 (0)