Private/Logic/RuntimeKernel/Execute/Manifested.PythonRuntime.ps1
|
function Get-ManifestedPythonRuntimePthPath { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$PythonHome ) if (-not (Test-Path -LiteralPath $PythonHome)) { return $null } $pthFile = @(Get-ChildItem -LiteralPath $PythonHome -File -Filter 'python*._pth' -ErrorAction SilentlyContinue | Sort-Object -Property Name | Select-Object -First 1) if (-not $pthFile) { return $null } return $pthFile[0].FullName } function Test-ManifestedPythonSiteImports { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$PythonHome ) $pthPath = Get-ManifestedPythonRuntimePthPath -PythonHome $PythonHome if ([string]::IsNullOrWhiteSpace($pthPath) -or -not (Test-Path -LiteralPath $pthPath)) { return [pscustomobject]@{ Exists = $false PthPath = $pthPath ImportSiteEnabled = $false SitePackagesPathListed = $false IsReady = $false } } $lines = @(Get-Content -LiteralPath $pthPath -ErrorAction SilentlyContinue) $importSiteEnabled = $false $sitePackagesPathListed = $false foreach ($line in $lines) { $trimmedLine = $line.Trim() if ($trimmedLine -eq 'import site') { $importSiteEnabled = $true } elseif ($trimmedLine -ieq 'Lib\site-packages') { $sitePackagesPathListed = $true } } return [pscustomobject]@{ Exists = $true PthPath = $pthPath ImportSiteEnabled = $importSiteEnabled SitePackagesPathListed = $sitePackagesPathListed IsReady = ($importSiteEnabled -and $sitePackagesPathListed) } } function Enable-ManifestedPythonSiteImports { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$PythonHome ) $pthState = Test-ManifestedPythonSiteImports -PythonHome $PythonHome if (-not $pthState.Exists) { throw "Could not find the Python runtime ._pth file under $PythonHome." } $sitePackagesRoot = Join-Path $PythonHome 'Lib\site-packages' New-ManifestedDirectory -Path $sitePackagesRoot | Out-Null $lines = @(Get-Content -LiteralPath $pthState.PthPath -ErrorAction Stop) $updatedLines = New-Object System.Collections.Generic.List[string] $hasImportSite = $false $hasSitePackages = $false foreach ($line in $lines) { $trimmedLine = $line.Trim() if ($trimmedLine -match '^(#\s*)?import\s+site$') { if (-not $hasImportSite) { $updatedLines.Add('import site') | Out-Null $hasImportSite = $true } continue } if ($trimmedLine -ieq 'Lib\site-packages') { if (-not $hasSitePackages) { $updatedLines.Add('Lib\site-packages') | Out-Null $hasSitePackages = $true } continue } $updatedLines.Add($line) | Out-Null } if (-not $hasSitePackages) { $updatedLines.Add('Lib\site-packages') | Out-Null } if (-not $hasImportSite) { $updatedLines.Add('import site') | Out-Null } Set-Content -LiteralPath $pthState.PthPath -Value @($updatedLines) -Encoding ASCII return (Test-ManifestedPythonSiteImports -PythonHome $PythonHome) } function Save-ManifestedPythonGetPipScript { [CmdletBinding()] param( [string]$LocalRoot = (Get-ManifestedLocalRoot) ) $layout = Get-ManifestedLayout -LocalRoot $LocalRoot $scriptPath = Join-Path $layout.PythonCacheRoot 'get-pip.py' $downloadPath = Get-ManifestedDownloadPath -TargetPath $scriptPath New-ManifestedDirectory -Path $layout.PythonCacheRoot | Out-Null $action = 'ReusedCache' if (-not (Test-Path -LiteralPath $scriptPath)) { Remove-ManifestedPath -Path $downloadPath | Out-Null try { Write-Host 'Downloading get-pip.py bootstrap script...' Enable-ManifestedTls12Support Invoke-WebRequestEx -Uri 'https://bootstrap.pypa.io/get-pip.py' -Headers @{ 'User-Agent' = 'Eigenverft.Manifested.Sandbox' } -OutFile $downloadPath -UseBasicParsing Move-Item -LiteralPath $downloadPath -Destination $scriptPath -Force $action = 'Downloaded' } catch { Remove-ManifestedPath -Path $downloadPath | Out-Null if (-not (Test-Path -LiteralPath $scriptPath)) { throw } Write-Warning ('Could not refresh get-pip.py. Using cached copy. ' + $_.Exception.Message) $action = 'ReusedCache' } } return [pscustomobject]@{ Path = $scriptPath Action = $action Uri = 'https://bootstrap.pypa.io/get-pip.py' } } function Ensure-ManifestedPythonPip { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$PythonExe, [Parameter(Mandatory = $true)] [string]$PythonHome, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) $siteState = Enable-ManifestedPythonSiteImports -PythonHome $PythonHome if (-not $siteState.IsReady) { throw "Python site import enablement failed for $PythonHome." } $pipProxyConfiguration = Get-ManifestedPipProxyConfigurationStatus -PythonExe $PythonExe -LocalRoot $LocalRoot if ($pipProxyConfiguration.Action -eq 'NeedsManagedProxy') { $pipProxyConfiguration = Sync-ManifestedPipProxyConfiguration -PythonExe $PythonExe -Status $pipProxyConfiguration -LocalRoot $LocalRoot } $existingPipProbe = Get-ManifestedPythonPipVersionProbe -PythonExe $PythonExe -LocalRoot $LocalRoot $existingPipVersion = $existingPipProbe.PipVersion if (-not [string]::IsNullOrWhiteSpace($existingPipVersion)) { $wrapperInfo = Set-ManifestedManagedPipWrappers -PythonHome $PythonHome -LocalRoot $LocalRoot return [pscustomobject]@{ Action = 'Reused' Bootstrap = 'Existing' PipVersion = $existingPipVersion GetPipScript = $null WrapperInfo = $wrapperInfo PipProxyConfiguration = $pipProxyConfiguration ExistingPipProbe = $existingPipProbe SiteImports = $siteState } } $bootstrap = 'EnsurePip' $ensurePipResult = Invoke-ManifestedPipAwarePythonCommand -PythonExe $PythonExe -Arguments @('-m', 'ensurepip', '--default-pip') -LocalRoot $LocalRoot $pipVersion = Get-ManifestedPythonPipVersion -PythonExe $PythonExe -LocalRoot $LocalRoot $getPipScript = $null $getPipResult = $null if ($ensurePipResult.ExitCode -ne 0 -or [string]::IsNullOrWhiteSpace($pipVersion)) { $bootstrap = 'GetPip' $getPipScript = Save-ManifestedPythonGetPipScript -LocalRoot $LocalRoot $getPipResult = Invoke-ManifestedPipAwarePythonCommand -PythonExe $PythonExe -Arguments @($getPipScript.Path) -LocalRoot $LocalRoot if ($getPipResult.ExitCode -ne 0) { throw (New-ManifestedPythonRuntimeValidationFailureMessage -Operation 'get-pip bootstrap' -PythonHome $PythonHome -CommandResult $getPipResult -SiteImportsState $siteState) } $pipVersion = Get-ManifestedPythonPipVersion -PythonExe $PythonExe -LocalRoot $LocalRoot } if ([string]::IsNullOrWhiteSpace($pipVersion)) { $bootstrapCommandResult = if ($bootstrap -eq 'EnsurePip') { $ensurePipResult } else { $getPipResult } throw (New-ManifestedPythonRuntimeValidationFailureMessage -Operation 'pip bootstrap' -PythonHome $PythonHome -CommandResult $bootstrapCommandResult -SiteImportsState $siteState) } $wrapperInfo = Set-ManifestedManagedPipWrappers -PythonHome $PythonHome -LocalRoot $LocalRoot return [pscustomobject]@{ Action = if ($bootstrap -eq 'EnsurePip') { 'InstalledEnsurePip' } else { 'InstalledGetPip' } Bootstrap = $bootstrap PipVersion = $pipVersion GetPipScript = $getPipScript WrapperInfo = $wrapperInfo PipProxyConfiguration = $pipProxyConfiguration SiteImports = $siteState } } function Install-ManifestedPythonEmbeddableRuntime { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject]$Definition, [Parameter(Mandatory = $true)] [pscustomobject]$PackageInfo, [string]$Flavor = (Get-ManifestedDefinitionFlavor -Definition $Definition), [string]$LocalRoot = (Get-ManifestedLocalRoot), [switch]$ForceInstall ) if ($PackageInfo.PSObject.Properties['Flavor'] -and -not [string]::IsNullOrWhiteSpace($PackageInfo.Flavor)) { $Flavor = $PackageInfo.Flavor } $versionSpec = Get-ManifestedVersionSpec -Definition $Definition $pythonHome = Get-ManifestedManagedPythonRuntimeHome -Version $PackageInfo.Version -Flavor $Flavor -LocalRoot $LocalRoot $currentValidation = Test-ManifestedPythonRuntimeHome -PythonHome $pythonHome -VersionSpec $versionSpec -LocalRoot $LocalRoot $siteState = $null if ($ForceInstall -or $currentValidation.Status -ne 'Ready') { New-ManifestedDirectory -Path (Split-Path -Parent $pythonHome) | Out-Null $stagePrefix = 'python' $installBlock = Get-ManifestedDefinitionBlock -Definition $Definition -SectionName 'install' -BlockName 'pythonEmbeddableZip' if ($installBlock -and $installBlock.PSObject.Properties.Match('stagePrefix').Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($installBlock.stagePrefix)) { $stagePrefix = [string]$installBlock.stagePrefix } $stageInfo = $null try { $stageInfo = Expand-ManifestedArchiveToStage -PackagePath $PackageInfo.Path -Prefix $stagePrefix if (-not (Test-Path -LiteralPath $stageInfo.ExpandedRoot)) { throw 'The Python embeddable ZIP did not extract as expected.' } if (Test-Path -LiteralPath $pythonHome) { Remove-ManifestedPath -Path $pythonHome | Out-Null } New-ManifestedDirectory -Path $pythonHome | Out-Null Get-ChildItem -LiteralPath $stageInfo.ExpandedRoot -Force | ForEach-Object { Move-Item -LiteralPath $_.FullName -Destination $pythonHome -Force } $siteState = Enable-ManifestedPythonSiteImports -PythonHome $pythonHome if (-not $siteState.IsReady) { throw "Python site import enablement failed for $pythonHome." } } finally { if ($stageInfo) { Remove-ManifestedPath -Path $stageInfo.StagePath | Out-Null } } } $pythonExe = Join-Path $pythonHome 'python.exe' $versionProbe = Get-ManifestedPythonReportedVersionProbe -PythonExe $pythonExe -LocalRoot $LocalRoot $reportedVersion = $versionProbe.ReportedVersion $reportedVersionObject = ConvertTo-ManifestedVersionObjectFromRule -VersionText $reportedVersion -Rule $versionSpec.RuntimeVersionRule $expectedVersionObject = ConvertTo-ManifestedVersionObjectFromRule -VersionText $PackageInfo.Version -Rule $versionSpec.RuntimeVersionRule if (-not $reportedVersionObject -or -not $expectedVersionObject -or $reportedVersionObject -ne $expectedVersionObject) { throw (New-ManifestedPythonRuntimeValidationFailureMessage -Operation 'post-install version check' -PythonHome $pythonHome -ExpectedVersion $PackageInfo.Version -ReportedVersion $reportedVersion -CommandResult $versionProbe.CommandResult -SiteImportsState $siteState) } $pipResult = Ensure-ManifestedPythonPip -PythonExe $pythonExe -PythonHome $pythonHome -LocalRoot $LocalRoot $validation = Test-ManifestedPythonRuntimeHome -PythonHome $pythonHome -VersionSpec $versionSpec -LocalRoot $LocalRoot if ($validation.Status -ne 'Ready') { $validationCommandResult = if ([string]::IsNullOrWhiteSpace($validation.ReportedVersion)) { $validation.VersionCommandResult } elseif ([string]::IsNullOrWhiteSpace($validation.PipVersion)) { $validation.PipCommandResult } else { $validation.VersionCommandResult } throw (New-ManifestedPythonRuntimeValidationFailureMessage -Operation 'post-pip validation' -PythonHome $pythonHome -ExpectedVersion $PackageInfo.Version -ReportedVersion $validation.ReportedVersion -CommandResult $validationCommandResult -SiteImportsState $validation.SiteImports) } return [pscustomobject]@{ Action = if ($ForceInstall -or $currentValidation.Status -ne 'Ready') { 'Installed' } else { 'Skipped' } Version = $PackageInfo.Version Flavor = $Flavor RuntimeHome = $pythonHome ExecutablePath = $validation.PythonExe PythonHome = $pythonHome PythonExe = $validation.PythonExe PipCmd = $validation.PipCmd Pip3Cmd = $validation.Pip3Cmd PthPath = $validation.PthPath PipVersion = $validation.PipVersion PipResult = $pipResult Source = $PackageInfo.Source } } |