diff --git a/.github/workflows/build_kernel_windows.yaml b/.github/workflows/build_kernel_windows.yaml new file mode 100644 index 00000000..24a36e65 --- /dev/null +++ b/.github/workflows/build_kernel_windows.yaml @@ -0,0 +1,86 @@ +name: "Build and test kernel - Windows" +on: + push: + branches: [main] + pull_request: + branches: [main] + types: [opened, synchronize, reopened] # trigger on PRs + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + os: [ windows-2022 ] + python: [ '3.12', '3.13' ] + torch: [ + { version: '2.8', cuda: '12.9.1', wheel: '129' } + ] + + name: Build kernel + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/cache@v4 + with: + key: cuda-toolkit-v${{ matrix.cuda }}-${{ matrix.os }} + path: | + C:\Program Files\NVIDIA GPU Computing Toolkit + ~/.cargo/registry + ~/.cargo/git + + - uses: actions/checkout@v5 + + # CUDA environment setup + - uses: N-Storm/cuda-toolkit@v0.2.28 + id: setup-cuda-toolkit + with: + cuda: ${{ matrix.torch.cuda }} # TODO(mfuntowicz): How can we test multiple CUDA versions than align with torch? + - name: "NVCC checks" + run: nvcc -V + + # Rust build environment setup + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Build build2cmake + run: ( cd build2cmake && cargo build --release ) + + # Python environment setup + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python }} + cache: 'pip' + + - name: Install PyTorch + run: pip install torch --index-url https://download.pytorch.org/whl/cu129 + + - name: Build activation kernel + run: ( scripts\windows\builder.ps1 -SourceFolder examples/activation -BuildConfig Release -Backend cuda -Build -Force ) +# - name: Copy activation kernel +# run: cp -rL examples/activation/build activation-kernel + + - name: Build cutlass GEMM kernel + run: ( scripts\windows\builder.ps1 -SourceFolder examples/cutlass-gemm -BuildConfig Release -Backend cuda -Build -Force ) +# - name: Copy cutlass GEMM kernel +# run: cp -rL examples/cutlass-gemm/result cutlass-gemm-kernel + + - name: Build relu kernel + run: ( scripts\windows\builder.ps1 -SourceFolder examples/relu -BuildConfig Release -Backend cuda -Build -Force ) +# - name: Copy relu kernel +# run: cp -rL examples/relu/result relu-kernel + + - name: Build relu-backprop-compile kernel + run: ( scripts\windows\builder.ps1 -SourceFolder examples/relu-backprop-compile -BuildConfig Release -Backend cuda -Build -Force ) +# - name: Copy relu-backprop-compile kernel +# run: cp -rL examples/relu-backprop-compile/result relu-backprop-compile-kernel + + # Just test that we build with the extra torchVersions argument. +# - name: Build relu kernel (specific Torch version) +# run: ( cd examples/relu-specific-torch && nix build . ) + + - name: Build silu-and-mul-universal kernel + run: ( scripts\windows\builder.ps1 -SourceFolder examples/silu-and-mul-universal -BuildConfig Release -Build -Force) \ No newline at end of file diff --git a/build2cmake/src/templates/cuda/preamble.cmake b/build2cmake/src/templates/cuda/preamble.cmake index f415c6db..077ce09b 100644 --- a/build2cmake/src/templates/cuda/preamble.cmake +++ b/build2cmake/src/templates/cuda/preamble.cmake @@ -98,3 +98,23 @@ else() ${GPU_LANG} "${${GPU_LANG}_SUPPORTED_ARCHS}") endif() + + +message(STATUS "Rendered for platform {{ platform }}") +{% if platform == 'windows' %} +include(${CMAKE_CURRENT_LIST_DIR}/cmake/windows.cmake) + +# Generate standardized build name +run_python(TORCH_VERSION "import torch; print(torch.__version__.split('+')[0])" "Failed to get Torch version") +run_python(CXX11_ABI_VALUE "import torch; print('TRUE' if torch._C._GLIBCXX_USE_CXX11_ABI else 'FALSE')" "Failed to get CXX11 ABI") +cmake_host_system_information(RESULT HOST_ARCH QUERY OS_PLATFORM) + +set(SYSTEM_STRING "${HOST_ARCH}-windows") + +if(GPU_LANG STREQUAL "CUDA") + generate_build_name(BUILD_VARIANT_NAME "${TORCH_VERSION}" ${CXX11_ABI_VALUE} "cuda" "${CUDA_VERSION}" "${SYSTEM_STRING}") +elseif(GPU_LANG STREQUAL "HIP") + run_python(ROCM_VERSION "import torch.version; print(torch.version.hip.split('.')[0] + '.' + torch.version.hip.split('.')[1])" "Failed to get ROCm version") + generate_build_name(BUILD_VARIANT_NAME "${TORCH_VERSION}" ${CXX11_ABI_VALUE} "rocm" "${ROCM_VERSION}" "${SYSTEM_STRING}") +endif() +{% endif %} diff --git a/build2cmake/src/templates/cuda/torch-extension.cmake b/build2cmake/src/templates/cuda/torch-extension.cmake index c52eb679..5b934e10 100644 --- a/build2cmake/src/templates/cuda/torch-extension.cmake +++ b/build2cmake/src/templates/cuda/torch-extension.cmake @@ -9,5 +9,17 @@ define_gpu_extension_target( USE_SABI 3 WITH_SOABI) -target_link_options({{ ops_name }} PRIVATE -static-libstdc++) +if( NOT MSVC) + target_link_options({{ ops_name }} PRIVATE -static-libstdc++) +endif() +{% if platform == 'windows' %} +# These methods below should be included from preamble.cmake on windows platform. + +# Add kernels_install target for huggingface/kernels library layout +add_kernels_install_target({{ ops_name }} "{{ name }}" "${BUILD_VARIANT_NAME}") + +# Add local_install target for local development with get_local_kernel() +add_local_install_target({{ ops_name }} "{{ name }}" "${BUILD_VARIANT_NAME}") + +{% endif %} diff --git a/build2cmake/src/templates/windows.cmake b/build2cmake/src/templates/windows.cmake new file mode 100644 index 00000000..ec313622 --- /dev/null +++ b/build2cmake/src/templates/windows.cmake @@ -0,0 +1,176 @@ +# Generate a standardized build variant name following the pattern: +# torch---windows +# +# Arguments: +# OUT_BUILD_NAME - Output variable name +# TORCH_VERSION - PyTorch version (e.g., "2.7.1") +# CXX11_ABI - Whether C++11 ABI is enabled (TRUE/FALSE) +# COMPUTE_FRAMEWORK - One of: cuda, rocm, metal, xpu +# COMPUTE_VERSION - Version of compute framework (e.g., "12.4" for CUDA, "6.0" for ROCm) +# Example output: torch271-cxx11-cu124-x86_64-windows +# +function(generate_build_name OUT_BUILD_NAME TORCH_VERSION CXX11_ABI COMPUTE_FRAMEWORK COMPUTE_VERSION) + # Flatten version by removing dots and padding to 2 components + string(REPLACE "." ";" VERSION_LIST "${TORCH_VERSION}") + list(LENGTH VERSION_LIST VERSION_COMPONENTS) + + # Pad to at least 2 components + if(VERSION_COMPONENTS LESS 2) + list(APPEND VERSION_LIST "0") + endif() + + # Take first 2 components and join without dots + list(GET VERSION_LIST 0 MAJOR) + list(GET VERSION_LIST 1 MINOR) + set(FLATTENED_TORCH "${MAJOR}${MINOR}") + + # Generate compute string + if(COMPUTE_FRAMEWORK STREQUAL "cuda") + # Flatten CUDA version (e.g., "12.4" -> "124") + string(REPLACE "." ";" COMPUTE_VERSION_LIST "${COMPUTE_VERSION}") + list(LENGTH COMPUTE_VERSION_LIST COMPUTE_COMPONENTS) + if(COMPUTE_COMPONENTS GREATER_EQUAL 2) + list(GET COMPUTE_VERSION_LIST 0 COMPUTE_MAJOR) + list(GET COMPUTE_VERSION_LIST 1 COMPUTE_MINOR) + set(COMPUTE_STRING "cu${COMPUTE_MAJOR}${COMPUTE_MINOR}") + else() + list(GET COMPUTE_VERSION_LIST 0 COMPUTE_MAJOR) + set(COMPUTE_STRING "cu${COMPUTE_MAJOR}0") + endif() + elseif(COMPUTE_FRAMEWORK STREQUAL "rocm") + # Flatten ROCm version (e.g., "6.0" -> "60") + string(REPLACE "." ";" COMPUTE_VERSION_LIST "${COMPUTE_VERSION}") + list(LENGTH COMPUTE_VERSION_LIST COMPUTE_COMPONENTS) + if(COMPUTE_COMPONENTS GREATER_EQUAL 2) + list(GET COMPUTE_VERSION_LIST 0 COMPUTE_MAJOR) + list(GET COMPUTE_VERSION_LIST 1 COMPUTE_MINOR) + set(COMPUTE_STRING "rocm${COMPUTE_MAJOR}${COMPUTE_MINOR}") + else() + list(GET COMPUTE_VERSION_LIST 0 COMPUTE_MAJOR) + set(COMPUTE_STRING "rocm${COMPUTE_MAJOR}0") + endif() + elseif(COMPUTE_FRAMEWORK STREQUAL "xpu") + # Flatten XPU version (e.g., "2025.2" -> "202552") + string(REPLACE "." ";" COMPUTE_VERSION_LIST "${COMPUTE_VERSION}") + list(LENGTH COMPUTE_VERSION_LIST COMPUTE_COMPONENTS) + if(COMPUTE_COMPONENTS GREATER_EQUAL 2) + list(GET COMPUTE_VERSION_LIST 0 COMPUTE_MAJOR) + list(GET COMPUTE_VERSION_LIST 1 COMPUTE_MINOR) + set(COMPUTE_STRING "xpu${COMPUTE_MAJOR}${COMPUTE_MINOR}") + else() + list(GET COMPUTE_VERSION_LIST 0 COMPUTE_MAJOR) + set(COMPUTE_STRING "xpu${COMPUTE_MAJOR}0") + endif() + else() + message(FATAL_ERROR "Unknown compute framework: ${COMPUTE_FRAMEWORK}") + endif() + + # Assemble the final build name + if(ABI_STRING STREQUAL "") + set(BUILD_NAME "torch${FLATTENED_TORCH}-${COMPUTE_STRING}-windows") + else() + set(BUILD_NAME "torch${FLATTENED_TORCH}-${ABI_STRING}-${COMPUTE_STRING}-windows") + endif() + + set(${OUT_BUILD_NAME} "${BUILD_NAME}" PARENT_SCOPE) + message(STATUS "Generated build name: ${BUILD_NAME}") +endfunction() + +# +# Create a custom install target for the huggingface/kernels library layout. +# This installs the extension into a directory structure suitable for kernel hub discovery: +# /// +# +# Arguments: +# TARGET_NAME - Name of the target to create the install rule for +# PACKAGE_NAME - Python package name (e.g., "activation") +# BUILD_VARIANT_NAME - Build variant name (e.g., "torch271-cxx11-cu124-x86_64-linux") +# INSTALL_PREFIX - Base installation directory (defaults to CMAKE_INSTALL_PREFIX) +# +function(add_kernels_install_target TARGET_NAME PACKAGE_NAME BUILD_VARIANT_NAME) + set(oneValueArgs INSTALL_PREFIX) + cmake_parse_arguments(ARG "" "${oneValueArgs}" "" ${ARGN}) + + if(NOT ARG_INSTALL_PREFIX) + set(ARG_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + endif() + + # Create the kernels_install target if it doesn't exist + if(NOT TARGET kernels_install) + add_custom_target(kernels_install ALL + COMMENT "Installing all kernels to hub-compatible layout" + VERBATIM) + endif() + + # Create a custom target for this specific kernel + set(KERNEL_INSTALL_TARGET "${TARGET_NAME}_kernel_install") + set(KERNEL_INSTALL_DIR "${ARG_INSTALL_PREFIX}/${BUILD_VARIANT_NAME}/${PACKAGE_NAME}") + + add_custom_target(${KERNEL_INSTALL_TARGET} ALL + COMMAND ${CMAKE_COMMAND} -E make_directory "${KERNEL_INSTALL_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy $ "${KERNEL_INSTALL_DIR}/" + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/torch-ext/${PACKAGE_NAME}" + "${KERNEL_INSTALL_DIR}/" + DEPENDS ${TARGET_NAME} + COMMENT "Installing ${TARGET_NAME} to ${KERNEL_INSTALL_DIR}" + VERBATIM) + + # Make kernels_install depend on this specific kernel's install + add_dependencies(kernels_install ${KERNEL_INSTALL_TARGET}) + + # Set folder for IDE organization + if(MSVC OR XCODE) + set_target_properties(${KERNEL_INSTALL_TARGET} PROPERTIES FOLDER "Install") + endif() + + message(STATUS "Added kernels_install target for ${TARGET_NAME} -> ${BUILD_VARIANT_NAME}/${PACKAGE_NAME}") +endfunction() + +# +# Add install rules for local development with huggingface/kernels. +# This installs the extension into the layout expected by get_local_kernel(): +# ${CMAKE_SOURCE_DIR}/build/// +# +# This allows developers to use get_local_kernel() from the kernels library to load +# locally built kernels without needing to publish to the hub. +# +# This uses the standard CMake install() command, so it works with the default +# "install" target that is always available. +# +# Arguments: +# TARGET_NAME - Name of the target to create the install rule for +# PACKAGE_NAME - Python package name (e.g., "activation") +# BUILD_VARIANT_NAME - Build variant name (e.g., "torch271-cxx11-cu124-x86_64-linux") +# +function(add_local_install_target TARGET_NAME PACKAGE_NAME BUILD_VARIANT_NAME) + # Define your local, folder based, installation directory + set(LOCAL_INSTALL_DIR "${CMAKE_SOURCE_DIR}/build/${BUILD_VARIANT_NAME}/${PACKAGE_NAME}") + + # Glob Python files at configure time + file(GLOB PYTHON_FILES "${CMAKE_SOURCE_DIR}/torch-ext/${PACKAGE_NAME}/*.py") + + # Create a custom target for local installation + add_custom_target(local_install + COMMENT "Installing files to local directory..." + ) + + # Add custom commands to copy files + add_custom_command(TARGET local_install POST_BUILD + # Copy the shared library + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + ${LOCAL_INSTALL_DIR}/ + + # Copy each Python file + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${PYTHON_FILES} + ${LOCAL_INSTALL_DIR}/ + + COMMENT "Copying shared library and Python files to ${LOCAL_INSTALL_DIR}" + COMMAND_EXPAND_LISTS + ) + + file(MAKE_DIRECTORY ${LOCAL_INSTALL_DIR}) + message(STATUS "Added install rules for ${TARGET_NAME} -> build/${BUILD_VARIANT_NAME}/${PACKAGE_NAME}") +endfunction() \ No newline at end of file diff --git a/build2cmake/src/torch/cuda.rs b/build2cmake/src/torch/cuda.rs index c58db682..08d4fb51 100644 --- a/build2cmake/src/torch/cuda.rs +++ b/build2cmake/src/torch/cuda.rs @@ -1,4 +1,5 @@ use std::collections::HashSet; +use std::env; use std::io::Write; use std::path::PathBuf; @@ -12,6 +13,7 @@ use crate::version::Version; use crate::FileSet; static CMAKE_UTILS: &str = include_str!("../templates/utils.cmake"); +static WINDOWS_UTILS: &str = include_str!("../templates/windows.cmake"); static REGISTRATION_H: &str = include_str!("../templates/registration.h"); static HIPIFY: &str = include_str!("../templates/cuda/hipify.py"); static CUDA_SUPPORTED_ARCHS_JSON: &str = include_str!("../cuda_supported_archs.json"); @@ -155,6 +157,13 @@ fn write_cmake( .entry(utils_path.clone()) .extend_from_slice(CMAKE_UTILS.as_bytes()); + let mut windows_utils_path = PathBuf::new(); + windows_utils_path.push("cmake"); + windows_utils_path.push("windows.cmake"); + file_set + .entry(windows_utils_path.clone()) + .extend_from_slice(WINDOWS_UTILS.as_bytes()); + let mut hipify_path = PathBuf::new(); hipify_path.push("cmake"); hipify_path.push("hipify.py"); @@ -184,7 +193,7 @@ fn write_cmake( render_kernel(env, kernel_name, kernel, cmake_writer)?; } - render_extension(env, ops_name, cmake_writer)?; + render_extension(env, name, ops_name, cmake_writer)?; Ok(()) } @@ -351,11 +360,17 @@ pub fn render_kernel( Ok(()) } -pub fn render_extension(env: &Environment, ops_name: &str, write: &mut impl Write) -> Result<()> { +pub fn render_extension( + env: &Environment, + name: &str, + ops_name: &str, + write: &mut impl Write, +) -> Result<()> { env.get_template("cuda/torch-extension.cmake") .wrap_err("Cannot get Torch extension template")? .render_to_write( context! { + name => name, ops_name => ops_name, }, &mut *write, @@ -382,7 +397,7 @@ pub fn render_preamble( cuda_minver => cuda_minver.map(|v| v.to_string()), cuda_maxver => cuda_maxver.map(|v| v.to_string()), cuda_supported_archs => cuda_supported_archs(), - + platform => env::consts::OS }, &mut *write, ) diff --git a/build2cmake/src/torch/xpu.rs b/build2cmake/src/torch/xpu.rs index 1eaafb0f..e7c44672 100644 --- a/build2cmake/src/torch/xpu.rs +++ b/build2cmake/src/torch/xpu.rs @@ -159,7 +159,7 @@ fn write_cmake( render_kernel(env, kernel_name, kernel, cmake_writer)?; } - render_extension(env, ops_name, cmake_writer)?; + render_extension(env, name, ops_name, cmake_writer)?; Ok(()) } @@ -250,11 +250,17 @@ pub fn render_kernel( Ok(()) } -pub fn render_extension(env: &Environment, ops_name: &str, write: &mut impl Write) -> Result<()> { +pub fn render_extension( + env: &Environment, + name: &str, + ops_name: &str, + write: &mut impl Write, +) -> Result<()> { env.get_template("xpu/torch-extension.cmake") .wrap_err("Cannot get Torch extension template")? .render_to_write( context! { + name => name, ops_name => ops_name, }, &mut *write, diff --git a/scripts/windows/builder.ps1 b/scripts/windows/builder.ps1 new file mode 100644 index 00000000..16ea060a --- /dev/null +++ b/scripts/windows/builder.ps1 @@ -0,0 +1,556 @@ +#Requires -Version 7.0 + +<# +.SYNOPSIS + Kernel Builder - Modern PowerShell wrapper for build2cmake tool + +.DESCRIPTION + This script provides a modular interface to build2cmake for generating CMake + structures from build.toml configuration files. Supports multiple backends + including CUDA, ROCm, Metal, and XPU. + +.PARAMETER SourceFolder + Path to the folder containing build.toml file + +.PARAMETER TargetFolder + Optional destination folder for generated CMake files (defaults to SourceFolder) + +.PARAMETER Backend + Target backend: cuda, rocm, metal, xpu, or universal + +.PARAMETER Build2CmakePath + Path to build2cmake executable (auto-detected if not specified) + +.PARAMETER Force + Force overwrite existing files without prompting + +.PARAMETER OpsId + Optional unique identifier suffixed to kernel names (e.g., Git SHA) + +.PARAMETER Clean + Remove generated artifacts instead of building + +.PARAMETER DryRun + Show what would be done without executing (clean mode only) + +.PARAMETER Validate + Validate build.toml without generating files + +.PARAMETER Build + Build the project after generating CMake files + +.PARAMETER BuildConfig + CMake build configuration (Debug or Release, defaults to Release) + +.PARAMETER ArchList + GPU architectures to build for (backend-agnostic). + For CUDA: e.g., "7.5 8.6" or "Turing Ampere" + For ROCm: e.g., "gfx906;gfx908;gfx90a" + For XPU: Currently not supported via environment variable + +.PARAMETER LocalInstall + Run CMake install target after building (installs to build/// for local development) + +.PARAMETER KernelsInstall + Run kernels_install target after building (installs to CMAKE_INSTALL_PREFIX///) + +.PARAMETER InstallPrefix + Installation prefix for kernels_install target (defaults to CMAKE_INSTALL_PREFIX) + +.EXAMPLE + .\builder.ps1 -SourceFolder ./examples/relu + +.EXAMPLE + .\builder.ps1 -SourceFolder ./examples/relu -Backend cuda -Force + +.EXAMPLE + .\builder.ps1 -SourceFolder ./examples/relu -TargetFolder ./build/relu -OpsId abc123 + +.EXAMPLE + .\builder.ps1 -SourceFolder ./examples/relu -Clean -Force + +.EXAMPLE + .\builder.ps1 -SourceFolder ./examples/relu -Backend cuda -Build -BuildConfig Debug + +.EXAMPLE + .\builder.ps1 -SourceFolder ./examples/relu -Backend cuda -ArchList "7.5 8.6" -Build + +.EXAMPLE + .\builder.ps1 -SourceFolder ./examples/relu -Backend rocm -ArchList "gfx906;gfx908" -Build + +.EXAMPLE + .\builder.ps1 -SourceFolder ./examples/relu -Backend cuda -Build -LocalInstall + +.EXAMPLE + .\builder.ps1 -SourceFolder ./examples/relu -Backend cuda -Build -KernelsInstall -InstallPrefix "C:\kernels" +#> + +[CmdletBinding(DefaultParameterSetName = 'Generate')] +param( + [Parameter(Mandatory = $true, Position = 0, HelpMessage = "Folder containing build.toml")] + [ValidateScript({ Test-Path $_ -PathType Container })] + [string]$SourceFolder, + + [Parameter(ParameterSetName = 'Generate')] + [ValidateScript({ + if ($_ -and !(Test-Path $_ -PathType Container)) { + throw "Target folder does not exist: $_" + } + $true + })] + [string]$TargetFolder, + + [Parameter(ParameterSetName = 'Generate')] + [ValidateSet('cuda', 'rocm', 'metal', 'xpu', 'universal')] + [string]$Backend, + + [Parameter()] + [string]$Build2CmakePath, + + [Parameter(ParameterSetName = 'Generate')] + [switch]$Force, + + [Parameter(ParameterSetName = 'Generate')] + [string]$OpsId, + + [Parameter(ParameterSetName = 'Generate')] + [switch]$Build, + + [Parameter(ParameterSetName = 'Generate')] + [ValidateSet('Debug', 'Release')] + [string]$BuildConfig = 'Release', + + [Parameter(ParameterSetName = 'Generate')] + [string]$ArchList, + + [Parameter(ParameterSetName = 'Generate')] + [switch]$LocalInstall, + + [Parameter(ParameterSetName = 'Generate')] + [switch]$KernelsInstall, + + [Parameter(ParameterSetName = 'Generate')] + [string]$InstallPrefix, + + [Parameter(ParameterSetName = 'Clean', Mandatory = $true)] + [switch]$Clean, + + [Parameter(ParameterSetName = 'Clean')] + [switch]$DryRun, + + [Parameter(ParameterSetName = 'Validate', Mandatory = $true)] + [switch]$Validate +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +#region Helper Functions + +function Write-Status { + param([string]$Message, [string]$Type = 'Info') + + $colors = @{ + 'Info' = 'Cyan' + 'Success' = 'Green' + 'Warning' = 'Yellow' + 'Error' = 'Red' + } + + $prefix = switch ($Type) { + 'Info' { '[*]' } + 'Success' { '[+]' } + 'Warning' { '[!]' } + 'Error' { '[X]' } + } + + Write-Host "$prefix $Message" -ForegroundColor $colors[$Type] +} + +function Find-Build2Cmake { + <# + .SYNOPSIS + Locates build2cmake executable in common locations + #> + + # Check if provided path is valid + if ($Build2CmakePath) { + if (Test-Path $Build2CmakePath -PathType Leaf) { + return $Build2CmakePath + } + throw "Specified build2cmake path not found: $Build2CmakePath" + } + + # Search common locations + $searchPaths = @( + (Join-Path $PSScriptRoot '..' '..' 'build2cmake' 'target' 'release' 'build2cmake.exe'), + (Join-Path $PSScriptRoot '..' '..' 'build2cmake' 'target' 'debug' 'build2cmake.exe'), + 'build2cmake.exe', + 'build2cmake' + ) + + foreach ($path in $searchPaths) { + $resolved = if ([System.IO.Path]::IsPathRooted($path)) { + $path + } else { + Join-Path $PWD $path + } + + if (Test-Path $resolved -PathType Leaf) { + Write-Status "Found build2cmake at: $resolved" -Type Info + return $resolved + } + } + + # Try system PATH + $cmd = Get-Command build2cmake -ErrorAction SilentlyContinue + if ($cmd) { + Write-Status "Using build2cmake from PATH: $($cmd.Source)" -Type Info + return $cmd.Source + } + + throw "build2cmake executable not found. Please build it or specify -Build2CmakePath" +} + +function Get-BuildTomlPath { + param([string]$Folder) + + $buildTomlPath = Join-Path $Folder 'build.toml' + + if (!(Test-Path $buildTomlPath -PathType Leaf)) { + throw "build.toml not found in folder: $Folder" + } + + return $buildTomlPath +} + +function Invoke-Build2Cmake { + param( + [string]$Build2CmakeExe, + [string[]]$Arguments + ) + + Write-Status "Executing: $Build2CmakeExe $($Arguments -join ' ')" -Type Info + + & $Build2CmakeExe @Arguments + + if ($LASTEXITCODE -ne 0) { + throw "build2cmake failed with exit code $LASTEXITCODE" + } +} + +function Import-EnvironmentVariables { + <# + .SYNOPSIS + Imports environment variables from a file + #> + param([string]$FilePath) + + Get-Content $FilePath | ForEach-Object { + if ($_ -match '^([^=]+)=(.*)$') { + Set-Item -Path "env:$($matches[1])" -Value $matches[2] + } + } +} + +function Initialize-VSEnvironment { + <# + .SYNOPSIS + Initializes Visual Studio build environment for MSBuild/CMake + #> + + Write-Status "Initializing Visual Studio environment..." -Type Info + + # Check if already in VS environment + if ($env:VSINSTALLDIR) { + Write-Status "Visual Studio environment already initialized" -Type Info + return + } + + # Search for vswhere.exe + $vswherePath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + if (!(Test-Path $vswherePath)) { + throw "vswhere.exe not found. Please install Visual Studio 2017 or later." + } + + # Find latest VS installation + $vsPath = & $vswherePath -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if (!$vsPath) { + throw "Visual Studio with C++ tools not found. Please install Visual Studio with C++ workload." + } + + Write-Status "Found Visual Studio at: $vsPath" -Type Info + + # Find vcvarsall.bat + $vcvarsPath = Join-Path $vsPath "VC\Auxiliary\Build\vcvarsall.bat" + if (!(Test-Path $vcvarsPath)) { + throw "vcvarsall.bat not found at expected location: $vcvarsPath" + } + + # Execute vcvarsall and capture environment variables + $tempFile = [System.IO.Path]::GetTempFileName() + + # Run vcvarsall.bat and export environment to temp file + cmd /c "`"$vcvarsPath`" x64 && set > `"$tempFile`"" + + if ($LASTEXITCODE -ne 0) { + Remove-Item $tempFile -ErrorAction SilentlyContinue + throw "Failed to initialize Visual Studio environment" + } + + # Parse and apply environment variables + Import-EnvironmentVariables -FilePath $tempFile + + Remove-Item $tempFile -ErrorAction SilentlyContinue + + Write-Status "Visual Studio environment initialized successfully" -Type Success +} + +function Get-CMakeConfigureArgs { + <# + .SYNOPSIS + Builds CMake configuration arguments + #> + param( + [bool]$ShouldInstall, + [string]$InstallPrefix + ) + + $kwargs = @("..", "-G", "Visual Studio 17 2022", "-A", "x64") + + if ($ShouldInstall -and $InstallPrefix) { + $kwargs += "-DCMAKE_INSTALL_PREFIX=$InstallPrefix" + Write-Status "Setting CMAKE_INSTALL_PREFIX=$InstallPrefix" -Type Info + } + + return $kwargs +} + +function Invoke-CMakeTarget { + <# + .SYNOPSIS + Executes a CMake build target + #> + param( + [string]$Target, + [string]$BuildConfig, + [string]$DisplayName + ) + + Write-Status "Running $DisplayName..." -Type Info + cmake --build . --target $Target --config $BuildConfig + + if ($LASTEXITCODE -ne 0) { + throw "$DisplayName failed with exit code $LASTEXITCODE" + } + + Write-Status "$DisplayName completed successfully!" -Type Success +} + +function Invoke-CMakeBuild { + param( + [string]$SourcePath, + [string]$BuildConfig, + [bool]$RunLocalInstall = $false, + [bool]$RunKernelsInstall = $false, + [string]$InstallPrefix = $null + ) + + Write-Status "Building project with CMake..." -Type Info + Write-Status "Configuration: $BuildConfig" -Type Info + + # Ensure VS environment is initialized + Initialize-VSEnvironment + + # Create build directory + $buildDir = Join-Path $SourcePath "build" + if (!(Test-Path $buildDir)) { + New-Item -ItemType Directory -Path $buildDir | Out-Null + Write-Status "Created build directory: $buildDir" -Type Info + } + + # Configure with CMake + Write-Status "Configuring CMake project..." -Type Info + Push-Location $buildDir + try { + $configureArgs = Get-CMakeConfigureArgs -ShouldInstall ($RunKernelsInstall -or $RunLocalInstall) -InstallPrefix $InstallPrefix + + cmake @configureArgs + + if ($LASTEXITCODE -ne 0) { + throw "CMake configuration failed with exit code $LASTEXITCODE" + } + + # Build with CMake + Write-Status "Building project..." -Type Info + cmake --build . --config $BuildConfig + + if ($LASTEXITCODE -ne 0) { + throw "CMake build failed with exit code $LASTEXITCODE" + } + + Write-Status "Build completed successfully!" -Type Success + + # Run install targets if requested + if ($RunLocalInstall) { + Invoke-CMakeTarget -Target 'local_install' -BuildConfig $BuildConfig -DisplayName 'install target (local development layout)' + } + + if ($RunKernelsInstall) { + Invoke-CMakeTarget -Target 'kernels_install' -BuildConfig $BuildConfig -DisplayName 'kernels_install target' + } + } + finally { + Pop-Location + } +} + +#endregion + +#region Backend-Specific Functions + +function Invoke-Backend { + <# + .SYNOPSIS + Generates CMake files for specified backend + #> + param( + [string]$Build2CmakeExe, + [string]$BuildToml, + [string]$Target, + [hashtable]$Options, + [string]$Backend + ) + + $backendName = if ($Backend -eq 'universal') { 'Universal' } else { $Backend.ToUpper() } + Write-Status "Generating $backendName backend..." -Type Info + + $kwargs = @('generate-torch', $BuildToml) + + if ($Target) { $kwargs += $Target } + if ($Options.Force) { $kwargs += '--force' } + if ($Options.OpsId) { $kwargs += '--ops-id', $Options.OpsId } + if ($Backend -and $Backend -ne 'universal') { $kwargs += '--backend', $Backend } + + Invoke-Build2Cmake -Build2CmakeExe $Build2CmakeExe -Arguments $kwargs +} + +function Set-BackendArchitecture { + <# + .SYNOPSIS + Configures backend-specific architecture environment variables + #> + param( + [string]$Backend, + [string]$ArchList + ) + + $archMappings = @{ + 'cuda' = @{ Env = 'TORCH_CUDA_ARCH_LIST'; Supported = $true } + 'rocm' = @{ Env = 'PYTORCH_ROCM_ARCH'; Supported = $true } + 'xpu' = @{ Env = $null; Supported = $false; Message = 'no standard environment variable' } + } + + if ($mapping = $archMappings[$Backend.ToLower()]) { + if ($mapping.Supported) { + Set-Item "env:$($mapping.Env)" -Value $ArchList + Write-Status "Set $($mapping.Env)=$ArchList" -Type Info + } else { + Write-Status "ArchList not supported for $Backend backend ($($mapping.Message))" -Type Warning + } + } else { + Write-Status "ArchList not applicable for $Backend backend" -Type Warning + } +} + +#endregion + +#region Main Logic + +try { + # Resolve paths + $SourceFolder = Resolve-Path $SourceFolder -ErrorAction Stop + $buildTomlPath = Get-BuildTomlPath -Folder $SourceFolder + $build2cmakeExe = Find-Build2Cmake + + # Validate mode + if ($Validate) { + Write-Status "Validating $buildTomlPath..." -Type Info + Invoke-Build2Cmake -Build2CmakeExe $build2cmakeExe -Arguments @('validate', $buildTomlPath) + Write-Status "Validation successful!" -Type Success + exit 0 + } + + # Clean mode + if ($Clean) { + Write-Status "Cleaning generated artifacts..." -Type Warning + + $kwargs = @('clean', $buildTomlPath) + if ($TargetFolder) { $kwargs += $TargetFolder } + if ($DryRun) { $kwargs += '--dry-run' } + if ($Force) { $kwargs += '--force' } + if ($OpsId) { $kwargs += '--ops-id', $OpsId } + + Invoke-Build2Cmake -Build2CmakeExe $build2cmakeExe -Arguments $kwargs + Write-Status "Clean completed!" -Type Success + exit 0 + } + + # Generate mode + # Check for Metal backend on Windows + if ($Backend -and $Backend.ToLower() -eq 'metal') { + throw "Metal backend is not supported on Windows. Metal is only available on macOS." + } + + $options = @{ + Force = $Force.IsPresent + OpsId = $OpsId + } + + # Set architecture environment variables if ArchList is provided + if ($ArchList -and $Backend) { + Set-BackendArchitecture -Backend $Backend -ArchList $ArchList + } + + # Determine backend strategy + if ($Backend) { + # Explicit backend specified + $targetPath = if ($TargetFolder) { Resolve-Path $TargetFolder } else { $null } + Invoke-Backend -Build2CmakeExe $build2cmakeExe -BuildToml $buildTomlPath -Target $targetPath -Options $options -Backend $Backend.ToLower() + } else { + # Auto-detect backend from build.toml + Write-Status "Auto-detecting backend from build.toml..." -Type Info + + $kwargs = @('generate-torch', $buildTomlPath) + if ($TargetFolder) { $kwargs += (Resolve-Path $TargetFolder) } + if ($Force) { $kwargs += '--force' } + if ($OpsId) { $kwargs += '--ops-id', $OpsId } + + Invoke-Build2Cmake -Build2CmakeExe $build2cmakeExe -Arguments $kwargs + } + + Write-Status "Generation completed successfully!" -Type Success + + # Build if requested (skip if no CMakeLists.txt exists, e.g., universal backend) + if ($Build) { + $buildPath = if ($TargetFolder) { $TargetFolder } else { $SourceFolder } + $cmakeListsPath = Join-Path $buildPath "CMakeLists.txt" + + if (!(Test-Path $cmakeListsPath -PathType Leaf)) { + Write-Status "No CMakeLists.txt found, skipping build (likely universal backend)" -Type Info + } else { + Invoke-CMakeBuild ` + -SourcePath $buildPath ` + -BuildConfig $BuildConfig ` + -RunLocalInstall $LocalInstall.IsPresent ` + -RunKernelsInstall $KernelsInstall.IsPresent ` + -InstallPrefix $InstallPrefix + } + } + +} catch { + Write-Status "Error: $_" -Type Error + exit 1 +} + +#endregion \ No newline at end of file