From 522ea04eb48e7d9654dc007d39b1aaab7c458553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ange-Marie=20MAURIN=20-=20A2M=20Syst=C3=A8mes?= Date: Fri, 6 Nov 2026 21:47:25 +0100 Subject: [PATCH] =?UTF-8?q?build:=20script=20d'installation=20de=20cuDNN,?= =?UTF-8?q?=20fonctions=20de=20t=C3=A9l=C3=A9chargement=20communes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install-cudnn.ps1 : version la plus récente lue dans l'index NVIDIA, archive cuda13 téléchargée avec reprise et vérifiée par SHA256, licence NVIDIA affichée et acceptée par l'utilisateur, installation sans droits admin dans %LOCALAPPDATA%\SEQUA\sdk\cudnn, variable utilisateur CUDNN_PATH. Refuse de remplacer un dossier de destination non vide qui n'est pas une installation cuDNN. Fonctions de téléchargement et d'empreinte factorisées dans scripts/lib/SequaDownload.ps1 (install-cuda.ps1 inchangé fonctionnellement). Écrit par un subagent (DeepSeek V4.1 Flash), relu et corrigé. --- README.md | 1 + scripts/install-cuda.ps1 | 58 +----- scripts/install-cudnn.ps1 | 363 ++++++++++++++++++++++++++++++++++ scripts/lib/SequaDownload.ps1 | 70 +++++++ 4 files changed, 437 insertions(+), 55 deletions(-) create mode 100644 scripts/install-cudnn.ps1 create mode 100644 scripts/lib/SequaDownload.ps1 diff --git a/README.md b/README.md index abf3291..ff8ce2d 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Il n'y a pas d'intégration continue sur serveur : les vérifications se lancent | Windows, avant commit | `.\scripts\check.ps1` | idem + Node.js pour Studio | | Linux / macOS | `./scripts/build.sh []` | CMake ≥ 3.25, Ninja, GCC ou Clang | | Lancer Studio | `.\scripts\studio.ps1 [-Core ws://hôte:port]` ou `./scripts/studio.sh [ws://hôte:port]` | Node.js 24 | +| Windows, cuDNN pour l'inférence GPU | `.\scripts\install-cudnn.ps1 [-Version ] [-CudaMajor 12\|13] [-DryRun]` | CUDA Toolkit installé + GPU NVIDIA ; télécharge cuDNN (~1,3 Go), demande d'accepter la licence NVIDIA et définit `CUDNN_PATH` (utilisateur, sans droits admin) | Chaque script PowerShell affiche son aide avec `-Help`. diff --git a/scripts/install-cuda.ps1 b/scripts/install-cuda.ps1 index 6534932..a2bef4f 100644 --- a/scripts/install-cuda.ps1 +++ b/scripts/install-cuda.ps1 @@ -74,10 +74,9 @@ if ($Help) { } $ErrorActionPreference = "Stop" +. (Join-Path $PSScriptRoot "lib\SequaDownload.ps1") $nvidiaBase = "https://developer.download.nvidia.com/compute/cuda" -function Write-Step([string]$text) { Write-Host "==> $text" -ForegroundColor Cyan } - function Get-DriverCudaVersion { $smi = Get-Command nvidia-smi.exe -ErrorAction SilentlyContinue if (-not $smi) { throw "nvidia-smi introuvable : pilote NVIDIA absent." } @@ -112,54 +111,6 @@ function Get-InstallerInfo([string]$version) { } } -# Empreinte via .NET : Get-FileHash manque dans certains Windows PowerShell 5.1 -# (PSModulePath pointant vers les modules de PowerShell 7). -function Get-Md5([string]$path) { - $stream = [System.IO.File]::OpenRead($path) - $md5 = [System.Security.Cryptography.MD5]::Create() - try { - ([System.BitConverter]::ToString($md5.ComputeHash($stream))).Replace("-", "") - } - finally { - $md5.Dispose() - $stream.Dispose() - } -} - -function Get-RemoteSize([string]$url) { - $headers = & curl.exe -sIL --fail $url - if ($LASTEXITCODE -ne 0) { throw "Serveur injoignable : $url" } - $length = $headers | Select-String -Pattern '^Content-Length:\s*(\d+)' | Select-Object -Last 1 - if (-not $length) { throw "Taille du fichier inconnue (pas de Content-Length)." } - [int64]$length.Matches[0].Groups[1].Value -} - -# Telechargement avec reprise : curl -C - repart de la taille du fichier .part. -function Invoke-ResumableDownload([string]$url, [string]$destination, [int64]$expectedSize, [int]$maxAttempts) { - $part = "$destination.part" - for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { - $have = if (Test-Path $part) { (Get-Item $part).Length } else { 0 } - if ($have -eq $expectedSize) { break } - if ($have -gt $expectedSize) { - Write-Host " Fichier partiel plus grand que prevu : on recommence." -ForegroundColor Yellow - Remove-Item $part -Force - $have = 0 - } - $pct = [math]::Round(100.0 * $have / $expectedSize, 1) - Write-Host (" Tentative {0}/{1} : {2:N0} / {3:N0} octets ({4} %)" -f $attempt, $maxAttempts, $have, $expectedSize, $pct) - & curl.exe -L --fail -C - --retry 5 --retry-delay 5 --retry-all-errors --speed-limit 1024 --speed-time 60 -o $part $url - if ($LASTEXITCODE -ne 0) { - Write-Host " curl interrompu (code $LASTEXITCODE), reprise dans 10 s..." -ForegroundColor Yellow - Start-Sleep -Seconds 10 - } - } - $final = if (Test-Path $part) { (Get-Item $part).Length } else { 0 } - if ($final -ne $expectedSize) { - throw "Telechargement incomplet apres $maxAttempts tentatives ($final / $expectedSize octets). Relancer le script pour reprendre." - } - Move-Item $part $destination -Force -} - # --- Verifications ---------------------------------------------------------- if ($Version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+$') { throw "-Version attend une version complete, ex. 13.5.0 (recu : $Version)." } $wanted = [version]$Version @@ -208,16 +159,13 @@ if (Test-Path $installer) { Write-Step "Installateur deja present dans le cache" } else { - $drive = Get-PSDrive -Name ([System.IO.Path]::GetPathRoot($CacheDir).Substring(0, 1)) - if ($drive.Free -lt ($size * 2)) { - throw ("Espace disque insuffisant sur {0}: ({1:N1} Go libres, {2:N1} Go conseilles)." -f $drive.Name, ($drive.Free / 1GB), ($size * 2 / 1GB)) - } + Assert-FreeSpace -Path $CacheDir -Needed ($size * 2) Write-Step "Telechargement avec reprise" Invoke-ResumableDownload $info.Url $installer $size $MaxAttempts } Write-Step "Verification MD5" -$hash = Get-Md5 $installer +$hash = Get-FileDigest -Path $installer -Algorithm MD5 if ($hash -ne $info.Md5) { Remove-Item $installer -Force throw "MD5 incorrect ($hash) : fichier supprime, relancer le script pour le retelecharger." diff --git a/scripts/install-cudnn.ps1 b/scripts/install-cudnn.ps1 new file mode 100644 index 0000000..a80a992 --- /dev/null +++ b/scripts/install-cudnn.ps1 @@ -0,0 +1,363 @@ +# SPDX-License-Identifier: MPL-2.0 +# Messages en ASCII : compatible Windows PowerShell 5.1 sans BOM. + +<# +.SYNOPSIS + Telecharge (avec reprise) et installe cuDNN NVIDIA pour Windows x86_64. + +.DESCRIPTION + Utilise pour le prototype prototypes/gpu_interop (etape 3 : inference ONNX Runtime + sur GPU). cuDNN est un composant PROPRIETAIRE NVIDIA : il reste un plugin facultatif + de SEQUA (docs, par. 10.2). Le script n'accepte la licence qu'avec -AcceptLicense ou + apres confirmation interactive (O/N) ; il ne modifie jamais le PATH. + + Etapes : + 1. detecte la majeure CUDA via la variable machine CUDA_PATH (ou -CudaMajor) ; + 2. lit l'index officiel NVIDIA et, si -Version est absent, prend la version la plus + recente (tri par version, pas par chaine : 9.28 > 9.9) ; + 3. lit le manifeste redistrib_.json et l'entree Windows x86_64 / cuda ; + 4. telecharge l'archive (~1,3 Go) avec reprise : en cas de coupure, relancer le script + repart du dernier octet recu (curl -C -) ; + 5. verifie le SHA256 (le fichier est supprime s'il est faux) ; + 6. extrait dans un dossier temporaire a cote de la destination, affiche la licence + extraite et ses premieres lignes, puis demande confirmation (O/N) ; + 7. installe en aplatissant le dossier racine de l'archive : \bin, + \include, \lib ; + 8. sauf -NoEnv, definit la variable d'environnement UTILISATEUR CUDNN_PATH (aucun + droit administrateur requis) et n'ajoute PAS au PATH. + + A lancer dans une fenetre PowerShell externe (telechargement long). + +.PARAMETER Version + Version cuDNN (ex. 9.28.0). Defaut : la plus recente de l'index NVIDIA. + +.PARAMETER CudaMajor + Majeure CUDA du paquet a installer (12 ou 13). Defaut : deduite de CUDA_PATH machine. + +.PARAMETER InstallDir + Dossier d'installation. Defaut : %LOCALAPPDATA%\SEQUA\sdk\cudnn\_cuda. + +.PARAMETER CacheDir + Dossier de telechargement. Defaut : %LOCALAPPDATA%\SEQUA\downloads\cudnn. + +.PARAMETER ArchivePath + Installe depuis une archive locale deja telechargee, sans acces au manifeste. Aucune + empreinte connue : la verification SHA256 est clairement signalee comme ignoree. + +.PARAMETER DryRun + Affiche ce qui serait fait (URL, SHA256, taille, destination, etat du cache) sans rien + telecharger, extraire ni installer. + +.PARAMETER DownloadOnly + Telecharge et verifie l'archive sans l'extraire ni l'installer. + +.PARAMETER AcceptLicense + Accepte la licence cuDNN sans demande interactive. + +.PARAMETER NoEnv + N'ecrit pas la variable d'environnement utilisateur CUDNN_PATH (utile pour les tests). + +.PARAMETER Force + Reinstalle meme si une DLL cudnn64_*.dll est deja presente dans la destination. + +.PARAMETER Help + Affiche cette aide. + +.EXAMPLE + powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1 -DryRun + +.EXAMPLE + powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1 -DownloadOnly + +.EXAMPLE + powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1 + +.EXAMPLE + powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1 -ArchivePath C:\cudnn.zip -AcceptLicense -NoEnv -InstallDir C:\tmp\cudnn +#> + +[CmdletBinding()] +param( + [string]$Version = "", + [int]$CudaMajor = 0, + [string]$InstallDir = "", + [string]$CacheDir = (Join-Path $env:LOCALAPPDATA "SEQUA\downloads\cudnn"), + [string]$ArchivePath = "", + [switch]$DryRun, + [switch]$DownloadOnly, + [switch]$AcceptLicense, + [switch]$NoEnv, + [switch]$Force, + [Alias("h", "?")] + [switch]$Help +) + +if ($Help) { + Get-Help $PSCommandPath -Detailed + exit 0 +} + +$ErrorActionPreference = "Stop" +. (Join-Path $PSScriptRoot "lib\SequaDownload.ps1") +$nvidiaBase = "https://developer.download.nvidia.com/compute/cudnn/redist" +$maxAttempts = 50 + +# Majeure CUDA depuis CUDA_PATH (machine d'abord : c'est la variable installee par le +# CUDA Toolkit ; le processus courant peut ne pas encore l'avoir heritee). +function Get-CudaMajor { + foreach ($scope in @("Machine", "Process")) { + $path = [System.Environment]::GetEnvironmentVariable("CUDA_PATH", $scope) + if ($path -and $path -match 'v([0-9]+)\.[0-9]+') { return [int]$Matches[1] } + } + return 0 +} + +# Versions cuDNN publiees dans l'index, triees par version (9.28 > 9.9), sans doublon. +function Get-CudnnVersions { + $index = & curl.exe -sL --fail "$nvidiaBase/" + if ($LASTEXITCODE -ne 0) { throw "Index cuDNN injoignable : $nvidiaBase/" } + $index | + ForEach-Object { if ($_ -match 'redistrib_([0-9]+\.[0-9]+\.[0-9]+)\.json') { $Matches[1] } } | + Sort-Object { [version]$_ } -Unique +} + +# Manifeste officiel de la version : donne chemin relatif, sha256, md5, taille. +function Get-CudnnManifest([string]$version) { + $url = "$nvidiaBase/redistrib_$version.json" + $json = & curl.exe -sL --fail $url + if ($LASTEXITCODE -ne 0) { throw "Manifeste cuDNN introuvable : $url" } + ($json -join "`n") | ConvertFrom-Json +} + +# Extraction ZIP sans Expand-Archive (absent de certains Windows PowerShell 5.1) : +# .NET en premier, tar.exe de Windows 10+ en repli. +function Expand-ZipArchive([string]$archive, [string]$destination) { + New-Item -ItemType Directory -Force -Path $destination | Out-Null + try { + Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop + [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $destination) + return + } + catch { + Write-Host " Extraction .NET indisponible : repli sur tar.exe." -ForegroundColor Yellow + Remove-Item -Recurse -Force $destination -ErrorAction SilentlyContinue + New-Item -ItemType Directory -Force -Path $destination | Out-Null + } + & tar.exe -xf $archive -C $destination + if ($LASTEXITCODE -ne 0) { throw "Extraction impossible : $archive" } +} + +# L'archive cuDNN contient un dossier racine unique : on le detecte pour l'aplatir. +function Get-ExtractedRoot([string]$dir) { + $entries = @(Get-ChildItem -Force -LiteralPath $dir) + if ($entries.Count -eq 1 -and $entries[0].PSIsContainer) { return $entries[0].FullName } + return $dir +} + +# DLL cuDNN a la racine du dossier bin, ou dans un sous-dossier de version CUDA. +function Find-CudnnDll([string]$root) { + if (-not (Test-Path -LiteralPath $root)) { return @() } + @(Get-ChildItem -Path $root -Recurse -File -Filter "cudnn64_*.dll" -ErrorAction SilentlyContinue) +} + +# --- Detection de CUDA ------------------------------------------------------ +Write-Step "CUDA installe" +if ($CudaMajor -gt 0) { $cudaMajor = $CudaMajor } else { $cudaMajor = Get-CudaMajor } +if ($cudaMajor -gt 0) { + Write-Host " Majeure CUDA : $cudaMajor (CUDA_PATH)" +} +else { + Write-Host " Majeure CUDA inconnue (CUDA_PATH absent)." -ForegroundColor Yellow +} + +$fromArchive = -not [string]::IsNullOrWhiteSpace($ArchivePath) +if ($fromArchive) { + if (-not (Test-Path -LiteralPath $ArchivePath -PathType Leaf)) { throw "Archive introuvable : $ArchivePath" } + $archive = (Resolve-Path -LiteralPath $ArchivePath).Path +} + +# --- Version ---------------------------------------------------------------- +if ([string]::IsNullOrWhiteSpace($Version)) { + if ($fromArchive) { + if ((Split-Path -Leaf $archive) -match '([0-9]+\.[0-9]+\.[0-9]+)') { $Version = $Matches[1] } else { $Version = "local" } + } + else { + Write-Step "Index cuDNN" + $versions = @(Get-CudnnVersions) + if ($versions.Count -eq 0) { throw "Aucune version cuDNN dans l'index $nvidiaBase/." } + $Version = $versions[-1] + Write-Host " Derniere version : $Version" + } +} +if ($Version -ne "local" -and $Version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+$') { + throw "-Version attend une version x.y.z, ex. 9.28.0 (recu : $Version)." +} + +# --- Destination ------------------------------------------------------------ +if ([string]::IsNullOrWhiteSpace($InstallDir)) { + if ($cudaMajor -le 0) { throw "Destination indeterminable : fournir -CudaMajor (CUDA_PATH absent) ou -InstallDir." } + $InstallDir = Join-Path $env:LOCALAPPDATA "SEQUA\sdk\cudnn\${Version}_cuda${cudaMajor}" +} + +# --- Deja installe ---------------------------------------------------------- +$existing = @(Find-CudnnDll $InstallDir) +if ($existing.Count -gt 0 -and -not $Force) { + Write-Step "cuDNN deja installe" + Write-Host " Dossier : $InstallDir" -ForegroundColor Green + $existing | ForEach-Object { Write-Host " $($_.FullName)" } + Write-Host "Rien a faire (utiliser -Force pour reinstaller)." -ForegroundColor Green + exit 0 +} + +# --- Archive, manifeste, empreinte ------------------------------------------ +if ($fromArchive) { + $sha256 = $null + $size = (Get-Item -LiteralPath $archive).Length + $url = $archive + $download = $false +} +else { + Write-Step "Manifeste cuDNN $Version" + $manifest = Get-CudnnManifest $Version + $key = "cuda$cudaMajor" + $entry = $manifest.cudnn.'windows-x86_64'.$key + if (-not $entry) { + $available = ((Get-Member -InputObject $manifest.cudnn.'windows-x86_64' -MemberType NoteProperty).Name) -join ", " + throw "Pas de binaire cuDNN Windows x86_64 pour $key (disponibles : $available)." + } + $url = "$nvidiaBase/$($entry.relative_path)" + $sha256 = $entry.sha256.ToUpperInvariant() + $size = [int64]$entry.size + $archive = Join-Path $CacheDir (Split-Path -Leaf $entry.relative_path) + $download = $true +} + +Write-Step "cuDNN $Version (cuda$cudaMajor)" +Write-Host " URL : $url" +if ($sha256) { Write-Host " SHA256 : $sha256" } +else { Write-Host " SHA256 : inconnu (archive locale : verification d'empreinte ignoree)" -ForegroundColor Yellow } +Write-Host (" Taille : {0:N0} octets ({1:N1} Mo)" -f $size, ($size / 1MB)) +Write-Host " Archive : $archive" +Write-Host " Destination: $InstallDir" + +if ($DryRun) { + if ($download) { + $partial = "$archive.part" + if (Test-Path $archive) { Write-Host " Archive deja telechargee dans le cache." } + elseif (Test-Path $partial) { Write-Host (" Telechargement partiel : {0:N0} octets." -f (Get-Item $partial).Length) } + } + Write-Host "DryRun : rien n'a ete telecharge, extrait ni installe." -ForegroundColor Yellow + exit 0 +} + +# --- Telechargement et verification ----------------------------------------- +if ($download) { + New-Item -ItemType Directory -Force -Path $CacheDir | Out-Null + if (Test-Path $archive) { + Write-Step "Archive deja presente dans le cache" + } + else { + Assert-FreeSpace -Path $CacheDir -Needed ($size * 3) + Write-Step "Telechargement avec reprise" + Invoke-ResumableDownload $url $archive $size $maxAttempts + } + + Write-Step "Verification SHA256" + $hash = Get-FileDigest -Path $archive -Algorithm SHA256 + if ($hash -ne $sha256) { + Remove-Item $archive -Force + throw "SHA256 incorrect ($hash) : fichier supprime, relancer le script pour le retelecharger." + } + Write-Host " OK" +} + +if ($DownloadOnly) { + Write-Host "Archive prete : $archive" -ForegroundColor Green + exit 0 +} + +# --- Extraction dans un dossier temporaire a cote de la destination --------- +Write-Step "Extraction" +$parent = Split-Path -Parent $InstallDir +if (-not $parent) { $parent = "." } +New-Item -ItemType Directory -Force -Path $parent | Out-Null +$temp = Join-Path $parent ((Split-Path -Leaf $InstallDir) + ".tmp-" + [guid]::NewGuid().ToString("N").Substring(0, 8)) +if (Test-Path $temp) { Remove-Item -Recurse -Force $temp } +Expand-ZipArchive $archive $temp +$root = Get-ExtractedRoot $temp +Write-Host " Archive : $archive" +Write-Host " Temporaire : $temp" +Write-Host " Racine archive : $root" + +# --- Licence ---------------------------------------------------------------- +Write-Step "Licence cuDNN (proprietaire NVIDIA)" +$license = Get-ChildItem -Path $temp -Recurse -File -Filter "LICENSE.txt" -ErrorAction SilentlyContinue | Select-Object -First 1 +if (-not $license) { + Remove-Item -Recurse -Force $temp + throw "LICENSE.txt introuvable dans l'archive extraite : installation refusee." +} +Write-Host " Licence : $($license.FullName)" +Get-Content -LiteralPath $license.FullName -TotalCount 8 | ForEach-Object { Write-Host " $_" } +Write-Host " cuDNN est proprietaire NVIDIA : son usage est regi par cette licence." -ForegroundColor Yellow + +if (-not $AcceptLicense) { + $answer = Read-Host "Accepter la licence cuDNN et installer ? (O/N)" + if ($answer -notmatch '^(o|oui|y|yes)$') { + Remove-Item -Recurse -Force $temp + Write-Host "Licence refusee : aucune installation, dossier temporaire supprime." -ForegroundColor Yellow + exit 2 + } +} + +# --- Installation (aplatir le dossier racine de l'archive) ------------------- +Write-Step "Installation" +# On ne remplace qu'un dossier vide ou une installation cuDNN precedente : jamais un +# dossier quelconque passe par erreur dans -InstallDir. +if (Test-Path -LiteralPath $InstallDir) { + $isEmpty = @(Get-ChildItem -Force -LiteralPath $InstallDir).Count -eq 0 + $isCudnn = Test-Path -LiteralPath (Join-Path $InstallDir "include\cudnn.h") + if (-not $isEmpty -and -not $isCudnn) { + Remove-Item -Recurse -Force $temp + throw "$InstallDir existe, n'est pas vide et ne contient pas d'installation cuDNN : rien n'a ete supprime. Choisir un autre -InstallDir." + } + Remove-Item -Recurse -Force -LiteralPath $InstallDir +} +New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null +Get-ChildItem -Force -LiteralPath $root | ForEach-Object { Move-Item -LiteralPath $_.FullName -Destination $InstallDir } +Remove-Item -Recurse -Force $temp +Write-Host " Installe dans : $InstallDir" + +if (-not $NoEnv) { + [System.Environment]::SetEnvironmentVariable("CUDNN_PATH", $InstallDir, "User") + Write-Host " CUDNN_PATH (utilisateur) = $InstallDir" +} + +# --- Verification ----------------------------------------------------------- +Write-Step "Verification" +$dlls = @(Find-CudnnDll $InstallDir) +$header = Join-Path $InstallDir "include\cudnn.h" +if ($dlls.Count -gt 0) { + Write-Host " DLL cudnn64_*.dll :" -ForegroundColor Green + $dlls | ForEach-Object { Write-Host " $($_.FullName)" } +} +else { + Write-Host " Aucune cudnn64_*.dll trouvee sous $InstallDir" -ForegroundColor Red +} +if (Test-Path $header) { Write-Host " En-tete : $header" -ForegroundColor Green } +else { Write-Host " En-tete include\cudnn.h introuvable." -ForegroundColor Red } + +if ($dlls.Count -eq 0 -or -not (Test-Path $header)) { + Write-Host "" + Write-Host "Installation incomplete : verifier l'archive extraite." -ForegroundColor Red + exit 1 +} + +Write-Host "" +Write-Host "cuDNN $Version (cuda$cudaMajor) installe : $InstallDir" -ForegroundColor Green +if (-not $NoEnv) { + Write-Host "CUDNN_PATH (utilisateur) defini : ouvrir un NOUVEAU terminal pour l'utiliser." +} +else { + Write-Host "CUDNN_PATH non defini (-NoEnv)." +} +Write-Host "Ce script n'ajoute rien au PATH : pointer l'application sur bin\ (ou copier cudnn64_*.dll a cote de l'executable)." diff --git a/scripts/lib/SequaDownload.ps1 b/scripts/lib/SequaDownload.ps1 new file mode 100644 index 0000000..04dab4c --- /dev/null +++ b/scripts/lib/SequaDownload.ps1 @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: MPL-2.0 +# Fonctions de telechargement communes (install-cuda.ps1, install-cudnn.ps1). +# ASCII uniquement : compatible Windows PowerShell 5.1 sans BOM. +# Ce fichier n'est pas un script autonome : il se dot-source depuis un autre script. + +# Titre d'etape, meme style dans tous les scripts d'installation. +function Write-Step([string]$text) { + Write-Host "==> $text" -ForegroundColor Cyan +} + +# Taille distante via une requete HEAD (curl.exe), en suivant les redirections. +function Get-RemoteSize([string]$url) { + $headers = & curl.exe -sIL --fail $url + if ($LASTEXITCODE -ne 0) { throw "Serveur injoignable : $url" } + $length = $headers | Select-String -Pattern '^Content-Length:\s*(\d+)' | Select-Object -Last 1 + if (-not $length) { throw "Taille du fichier inconnue (pas de Content-Length)." } + [int64]$length.Matches[0].Groups[1].Value +} + +# Empreinte generique via .NET : Get-FileHash manque dans certains Windows PowerShell 5.1 +# (PSModulePath pointant vers les modules de PowerShell 7). +function Get-FileDigest([string]$path, [string]$Algorithm = "SHA256") { + switch ($Algorithm.ToUpperInvariant()) { + "SHA256" { $hasher = [System.Security.Cryptography.SHA256]::Create() } + "MD5" { $hasher = [System.Security.Cryptography.MD5]::Create() } + default { throw "Algorithme d'empreinte non gere : $Algorithm (SHA256 ou MD5)." } + } + $stream = [System.IO.File]::OpenRead($path) + try { + ([System.BitConverter]::ToString($hasher.ComputeHash($stream))).Replace("-", "") + } + finally { + $hasher.Dispose() + $stream.Dispose() + } +} + +# Verifie qu'il reste au moins $Needed octets libres sur le volume de $Path. +function Assert-FreeSpace([string]$Path, [int64]$Needed) { + $drive = Get-PSDrive -Name ([System.IO.Path]::GetPathRoot($Path).Substring(0, 1)) + if ($drive.Free -lt $Needed) { + throw ("Espace disque insuffisant sur {0}: ({1:N1} Go libres, {2:N1} Go conseilles)." -f $drive.Name, ($drive.Free / 1GB), ($Needed / 1GB)) + } +} + +# Telechargement avec reprise : curl -C - repart de la taille du fichier .part. +function Invoke-ResumableDownload([string]$url, [string]$destination, [int64]$expectedSize, [int]$maxAttempts) { + $part = "$destination.part" + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + $have = if (Test-Path $part) { (Get-Item $part).Length } else { 0 } + if ($have -eq $expectedSize) { break } + if ($have -gt $expectedSize) { + Write-Host " Fichier partiel plus grand que prevu : on recommence." -ForegroundColor Yellow + Remove-Item $part -Force + $have = 0 + } + $pct = [math]::Round(100.0 * $have / $expectedSize, 1) + Write-Host (" Tentative {0}/{1} : {2:N0} / {3:N0} octets ({4} %)" -f $attempt, $maxAttempts, $have, $expectedSize, $pct) + & curl.exe -L --fail -C - --retry 5 --retry-delay 5 --retry-all-errors --speed-limit 1024 --speed-time 60 -o $part $url + if ($LASTEXITCODE -ne 0) { + Write-Host " curl interrompu (code $LASTEXITCODE), reprise dans 10 s..." -ForegroundColor Yellow + Start-Sleep -Seconds 10 + } + } + $final = if (Test-Path $part) { (Get-Item $part).Length } else { 0 } + if ($final -ne $expectedSize) { + throw "Telechargement incomplet apres $maxAttempts tentatives ($final / $expectedSize octets). Relancer le script pour reprendre." + } + Move-Item $part $destination -Force +}