Private/Logic/RuntimeKernel/Facts/Manifested.RuntimeFacts.Python.ps1
|
function Get-ManifestedManagedPythonRuntimeHome { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Version, [Parameter(Mandatory = $true)] [string]$Flavor, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) $layout = Get-ManifestedLayout -LocalRoot $LocalRoot return (Join-Path $layout.PythonToolsRoot ($Version + '\' + $Flavor)) } function Get-ManifestedPythonReportedVersionProbe { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$PythonExe, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) if (-not (Test-Path -LiteralPath $PythonExe)) { return [pscustomobject]@{ ReportedVersion = $null CommandResult = $null } } $commandResult = Invoke-ManifestedPythonCommand -PythonExe $PythonExe -Arguments @('-c', 'import sys; print(*sys.version_info[:3], sep=chr(46))') -LocalRoot $LocalRoot $reportedVersion = $null if ($commandResult.ExitCode -eq 0) { $versionLine = @($commandResult.OutputLines | Select-Object -First 1) if ($versionLine) { $reportedVersion = $versionLine[0].ToString().Trim() } } return [pscustomobject]@{ ReportedVersion = $reportedVersion CommandResult = $commandResult } } function Get-ManifestedPythonReportedVersion { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$PythonExe, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) return (Get-ManifestedPythonReportedVersionProbe -PythonExe $PythonExe -LocalRoot $LocalRoot).ReportedVersion } function Get-ManifestedPythonPipVersionProbe { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$PythonExe, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) if (-not (Test-Path -LiteralPath $PythonExe)) { return [pscustomobject]@{ PipVersion = $null CommandResult = $null } } $commandResult = Invoke-ManifestedPipAwarePythonCommand -PythonExe $PythonExe -Arguments @('-m', 'pip', '--version') -LocalRoot $LocalRoot $pipVersion = $null if ($commandResult.ExitCode -eq 0) { $versionLine = @($commandResult.OutputLines | Select-Object -First 1) if ($versionLine) { $pipVersion = $versionLine[0].ToString().Trim() } } return [pscustomobject]@{ PipVersion = $pipVersion CommandResult = $commandResult } } function Get-ManifestedPythonPipVersion { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$PythonExe, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) return (Get-ManifestedPythonPipVersionProbe -PythonExe $PythonExe -LocalRoot $LocalRoot).PipVersion } function Get-ManifestedPythonCommandFailureHint { [CmdletBinding()] param( [pscustomobject]$CommandResult ) if (-not $CommandResult) { return $null } $combinedText = @( $CommandResult.ExceptionMessage $CommandResult.OutputText ) -join [Environment]::NewLine if ($combinedText -match 'No module named encodings|init_fs_encoding') { return 'The Python process started with an invalid import-path configuration. The managed runtime now clears PYTHONHOME and PYTHONPATH automatically; if this persists, repair the managed runtime cache and retry.' } return $null } function New-ManifestedPythonRuntimeValidationFailureMessage { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Operation, [Parameter(Mandatory = $true)] [string]$PythonHome, [string]$ExpectedVersion, [string]$ReportedVersion, [pscustomobject]$CommandResult, [pscustomobject]$SiteImportsState ) $lines = New-Object System.Collections.Generic.List[string] $lines.Add(("Python runtime validation failed during {0} at {1}." -f $Operation, $PythonHome)) | Out-Null if (-not [string]::IsNullOrWhiteSpace($ExpectedVersion)) { $lines.Add(("Expected version: {0}." -f $ExpectedVersion)) | Out-Null } if (-not [string]::IsNullOrWhiteSpace($ReportedVersion)) { $lines.Add(("Reported version: {0}." -f $ReportedVersion)) | Out-Null } if ($CommandResult -and $null -ne $CommandResult.ExitCode) { $lines.Add(("python.exe exit code: {0}." -f $CommandResult.ExitCode)) | Out-Null } if ($CommandResult -and -not [string]::IsNullOrWhiteSpace($CommandResult.ExceptionMessage)) { $lines.Add(("Startup error: {0}" -f $CommandResult.ExceptionMessage)) | Out-Null } if ($CommandResult -and -not [string]::IsNullOrWhiteSpace($CommandResult.OutputText)) { $lines.Add(("python.exe output:{0}{1}" -f [Environment]::NewLine, $CommandResult.OutputText)) | Out-Null } if ($SiteImportsState) { $lines.Add(("Site imports: import site={0}; Lib\\site-packages listed={1}; pth={2}." -f $SiteImportsState.ImportSiteEnabled, $SiteImportsState.SitePackagesPathListed, $SiteImportsState.PthPath)) | Out-Null } if ($CommandResult -and $CommandResult.IsManagedPython -and @($CommandResult.SanitizedVariables).Count -gt 0) { $lines.Add(("Managed runtime startup cleared: {0}." -f (@($CommandResult.SanitizedVariables) -join ', '))) | Out-Null } $hint = Get-ManifestedPythonCommandFailureHint -CommandResult $CommandResult if (-not [string]::IsNullOrWhiteSpace($hint)) { $lines.Add($hint) | Out-Null } return (@($lines) -join [Environment]::NewLine) } function Test-ManifestedPythonRuntimeHome { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$PythonHome, [Parameter(Mandatory = $true)] [pscustomobject]$VersionSpec, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) $pythonExe = Join-Path $PythonHome 'python.exe' $pipCmd = Join-Path $PythonHome 'pip.cmd' $pip3Cmd = Join-Path $PythonHome 'pip3.cmd' $siteState = Test-ManifestedPythonSiteImports -PythonHome $PythonHome $versionCommandResult = $null $pipCommandResult = $null if (-not (Test-Path -LiteralPath $PythonHome)) { $status = 'Missing' $reportedVersion = $null $pipVersion = $null } elseif (-not (Test-Path -LiteralPath $pythonExe)) { $status = 'NeedsRepair' $reportedVersion = $null $pipVersion = $null } else { $versionProbe = Get-ManifestedPythonReportedVersionProbe -PythonExe $pythonExe -LocalRoot $LocalRoot $reportedVersion = $versionProbe.ReportedVersion $versionCommandResult = $versionProbe.CommandResult $versionObject = ConvertTo-ManifestedVersionObjectFromRule -VersionText $reportedVersion -Rule $VersionSpec.RuntimeVersionRule $pipProbe = if ($siteState.ImportSiteEnabled) { Get-ManifestedPythonPipVersionProbe -PythonExe $pythonExe -LocalRoot $LocalRoot } else { $null } $pipVersion = if ($pipProbe) { $pipProbe.PipVersion } else { $null } $pipCommandResult = if ($pipProbe) { $pipProbe.CommandResult } else { $null } $hasWrappers = (Test-Path -LiteralPath $pipCmd) -and (Test-Path -LiteralPath $pip3Cmd) $status = if ($versionObject -and $siteState.IsReady -and $hasWrappers -and -not [string]::IsNullOrWhiteSpace($pipVersion)) { 'Ready' } else { 'NeedsRepair' } } return [pscustomobject]@{ Status = $status IsReady = ($status -eq 'Ready') PythonHome = $PythonHome PythonExe = $pythonExe PipCmd = $pipCmd Pip3Cmd = $pip3Cmd ReportedVersion = $reportedVersion PipVersion = $pipVersion PthPath = $siteState.PthPath SiteImports = $siteState VersionCommandResult = $versionCommandResult PipCommandResult = $pipCommandResult ValidationHint = if (-not [string]::IsNullOrWhiteSpace($reportedVersion) -and -not [string]::IsNullOrWhiteSpace($pipVersion)) { $null } elseif ($versionCommandResult -and [string]::IsNullOrWhiteSpace($reportedVersion)) { Get-ManifestedPythonCommandFailureHint -CommandResult $versionCommandResult } elseif ($pipCommandResult -and [string]::IsNullOrWhiteSpace($pipVersion)) { Get-ManifestedPythonCommandFailureHint -CommandResult $pipCommandResult } else { $null } } } function Get-ManifestedInstalledPythonRuntime { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject]$Definition, [string]$Flavor = (Get-ManifestedDefinitionFlavor -Definition $Definition), [string]$LocalRoot = (Get-ManifestedLocalRoot) ) $layout = Get-ManifestedLayout -LocalRoot $LocalRoot $versionSpec = Get-ManifestedVersionSpec -Definition $Definition $entries = @() if (Test-Path -LiteralPath $layout.PythonToolsRoot) { $versionRoots = Get-ChildItem -LiteralPath $layout.PythonToolsRoot -Directory -ErrorAction SilentlyContinue | Sort-Object -Descending -Property @{ Expression = { ConvertTo-ManifestedVersionObjectFromRule -VersionText $_.Name -Rule $versionSpec.RuntimeVersionRule } } foreach ($versionRoot in $versionRoots) { $pythonHome = Join-Path $versionRoot.FullName $Flavor if (-not (Test-Path -LiteralPath $pythonHome)) { continue } $validation = Test-ManifestedPythonRuntimeHome -PythonHome $pythonHome -VersionSpec $versionSpec -LocalRoot $layout.LocalRoot $expectedVersion = ConvertTo-ManifestedVersionObjectFromRule -VersionText $versionRoot.Name -Rule $versionSpec.RuntimeVersionRule $reportedVersion = ConvertTo-ManifestedVersionObjectFromRule -VersionText $validation.ReportedVersion -Rule $versionSpec.RuntimeVersionRule $versionMatches = (-not $reportedVersion) -or (-not $expectedVersion) -or ($reportedVersion -eq $expectedVersion) $entries += [pscustomobject]@{ Version = $versionRoot.Name Flavor = $Flavor PythonHome = $pythonHome PythonExe = $validation.PythonExe Validation = $validation VersionMatches = $versionMatches PipVersion = $validation.PipVersion IsReady = ($validation.IsReady -and $versionMatches) } } } return [pscustomobject]@{ Current = ($entries | Where-Object { $_.IsReady } | Select-Object -First 1) Valid = @($entries | Where-Object { $_.IsReady }) Invalid = @($entries | Where-Object { -not $_.IsReady }) } } function Get-ManifestedPythonExternalPaths { [CmdletBinding()] param( [string]$LocalRoot = (Get-ManifestedLocalRoot) ) $layout = Get-ManifestedLayout -LocalRoot $LocalRoot $candidatePaths = New-Object System.Collections.Generic.List[string] foreach ($commandName in @('python.exe', 'python')) { foreach ($command in @(Get-Command -Name $commandName -CommandType Application -All -ErrorAction SilentlyContinue)) { $commandPath = $null if ($command.PSObject.Properties['Path'] -and $command.Path) { $commandPath = $command.Path } elseif ($command.PSObject.Properties['Source'] -and $command.Source) { $commandPath = $command.Source } if (-not [string]::IsNullOrWhiteSpace($commandPath) -and $commandPath -like '*.exe') { $candidatePaths.Add($commandPath) | Out-Null } } } $additionalPatterns = @() if (-not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) { $additionalPatterns += (Join-Path $env:LOCALAPPDATA 'Programs\Python\Python*\python.exe') } if (-not [string]::IsNullOrWhiteSpace($env:ProgramFiles)) { $additionalPatterns += (Join-Path $env:ProgramFiles 'Python*\python.exe') } if (-not [string]::IsNullOrWhiteSpace($env:USERPROFILE)) { $additionalPatterns += (Join-Path $env:USERPROFILE '.pyenv\pyenv-win\versions\*\python.exe') } foreach ($pattern in $additionalPatterns) { foreach ($candidate in @(Get-ChildItem -Path $pattern -File -ErrorAction SilentlyContinue)) { $candidatePaths.Add($candidate.FullName) | Out-Null } } $resolvedPaths = New-Object System.Collections.Generic.List[string] foreach ($candidatePath in @($candidatePaths | Select-Object -Unique)) { $fullCandidatePath = Get-ManifestedFullPath -Path $candidatePath if ([string]::IsNullOrWhiteSpace($fullCandidatePath) -or -not (Test-Path -LiteralPath $fullCandidatePath)) { continue } if (Test-ManifestedPathIsUnderRoot -Path $fullCandidatePath -RootPath $layout.PythonToolsRoot) { continue } if ($fullCandidatePath -like '*\WindowsApps\python.exe') { continue } $resolvedPaths.Add($fullCandidatePath) | Out-Null } return @($resolvedPaths | Select-Object -Unique) } function Test-ManifestedExternalPythonRuntime { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$PythonExe, [Parameter(Mandatory = $true)] [pscustomobject]$VersionSpec, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) $versionProbe = Get-ManifestedPythonReportedVersionProbe -PythonExe $PythonExe -LocalRoot $LocalRoot $reportedVersion = $versionProbe.ReportedVersion $versionObject = ConvertTo-ManifestedVersionObjectFromRule -VersionText $reportedVersion -Rule $VersionSpec.RuntimeVersionRule $pipProbe = if ($versionObject -and (Test-ManifestedExternalVersion -Version $versionObject -VersionPolicy $VersionSpec.VersionPolicy -Rule $VersionSpec.RuntimeVersionRule)) { Get-ManifestedPythonPipVersionProbe -PythonExe $PythonExe -LocalRoot $LocalRoot } else { $null } $pipVersion = if ($pipProbe) { $pipProbe.PipVersion } else { $null } $isReady = ($versionObject -and (Test-ManifestedExternalVersion -Version $versionObject -VersionPolicy $VersionSpec.VersionPolicy -Rule $VersionSpec.RuntimeVersionRule) -and -not [string]::IsNullOrWhiteSpace($pipVersion)) return [pscustomobject]@{ Status = if ($isReady) { 'Ready' } else { 'Invalid' } IsReady = $isReady PythonHome = if (Test-Path -LiteralPath $PythonExe) { Split-Path -Parent $PythonExe } else { $null } PythonExe = $PythonExe ReportedVersion = if ($versionObject) { $versionObject.ToString() } else { $reportedVersion } PipVersion = $pipVersion VersionCommandResult = $versionProbe.CommandResult PipCommandResult = if ($pipProbe) { $pipProbe.CommandResult } else { $null } } } function Get-ManifestedPythonRuntimeFromCandidatePath { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$CandidatePath, [Parameter(Mandatory = $true)] [pscustomobject]$VersionSpec, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) $validation = Test-ManifestedExternalPythonRuntime -PythonExe $CandidatePath -VersionSpec $VersionSpec -LocalRoot $LocalRoot if (-not $validation.IsReady) { return $null } return [pscustomobject]@{ Version = $validation.ReportedVersion Flavor = $null PythonHome = $validation.PythonHome PythonExe = $validation.PythonExe Validation = $validation PipVersion = $validation.PipVersion IsReady = $true Source = 'External' Discovery = 'Path' } } function Get-ManifestedSystemPythonRuntime { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject]$Definition, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) $versionSpec = Get-ManifestedVersionSpec -Definition $Definition foreach ($candidatePath in @(Get-ManifestedPythonExternalPaths -LocalRoot $LocalRoot)) { $runtime = Get-ManifestedPythonRuntimeFromCandidatePath -CandidatePath $candidatePath -VersionSpec $versionSpec -LocalRoot $LocalRoot if ($runtime) { return $runtime } } return $null } function Get-ManifestedPythonEmbeddableRuntimeFactsFromDefinition { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [pscustomobject]$Definition, [string]$LocalRoot = (Get-ManifestedLocalRoot) ) $flavor = Get-ManifestedDefinitionFlavor -Definition $Definition $layout = $null try { $layout = Get-ManifestedLayout -LocalRoot $LocalRoot } catch { return (New-ManifestedRuntimeFacts -RuntimeName $Definition.runtimeName -CommandName $Definition.commandName -RuntimeKind 'PortablePackage' -LocalRoot $LocalRoot -Layout $layout -PlatformSupported:$false -BlockedReason $_.Exception.Message -AdditionalProperties @{ Flavor = $flavor Package = $null PackagePath = $null PipVersion = $null PipCmd = $null Pip3Cmd = $null InvalidRuntimeHomes = @() }) } $partialPaths = @() if (Test-Path -LiteralPath $layout.PythonCacheRoot) { $partialPaths += @(Get-ChildItem -LiteralPath $layout.PythonCacheRoot -File -Filter '*.download' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName) } $partialPaths += @(Get-ManifestedStageDirectories -Prefix 'python' -Mode TemporaryShort -LegacyRootPaths @($layout.ToolsRoot) | Select-Object -ExpandProperty FullName) $installed = Get-ManifestedInstalledPythonRuntime -Definition $Definition -Flavor $flavor -LocalRoot $layout.LocalRoot $managedRuntime = $installed.Current $externalRuntime = $null if (-not $managedRuntime) { $externalRuntime = Get-ManifestedSystemPythonRuntime -Definition $Definition -LocalRoot $layout.LocalRoot } $package = Get-LatestCachedZipArtifactFromDefinition -Definition $Definition -Flavor $flavor -LocalRoot $layout.LocalRoot $currentRuntime = if ($managedRuntime) { $managedRuntime } else { $externalRuntime } $invalidRuntimeHomes = @($installed.Invalid | Select-Object -ExpandProperty PythonHome) $executablePath = if ($currentRuntime) { $currentRuntime.PythonExe } else { $null } return (New-ManifestedRuntimeFacts -RuntimeName $Definition.runtimeName -CommandName $Definition.commandName -RuntimeKind 'PortablePackage' -LocalRoot $layout.LocalRoot -Layout $layout -ManagedRuntime $managedRuntime -ExternalRuntime $externalRuntime -Artifact $package -PartialPaths $partialPaths -InvalidPaths $invalidRuntimeHomes -Version $(if ($currentRuntime) { $currentRuntime.Version } elseif ($package) { $package.Version } else { $null }) -RuntimeHome $(if ($currentRuntime) { $currentRuntime.PythonHome } else { $null }) -RuntimeSource $(if ($managedRuntime) { 'Managed' } elseif ($externalRuntime) { 'External' } else { $null }) -ExecutablePath $executablePath -RuntimeValidation $(if ($currentRuntime) { $currentRuntime.Validation } else { $null }) -AdditionalProperties @{ Flavor = $flavor Package = $package PackagePath = if ($package) { $package.Path } else { $null } PipVersion = if ($currentRuntime -and $currentRuntime.PSObject.Properties['PipVersion']) { $currentRuntime.PipVersion } else { $null } PipCmd = if ($currentRuntime -and $currentRuntime.PSObject.Properties['PipCmd']) { $currentRuntime.PipCmd } else { $null } Pip3Cmd = if ($currentRuntime -and $currentRuntime.PSObject.Properties['Pip3Cmd']) { $currentRuntime.Pip3Cmd } else { $null } InvalidRuntimeHomes = $invalidRuntimeHomes }) } |