Skip to content

Commit 582f43d

Browse files
authored
Merge branch 'main' into backport/main/pr-1226
2 parents 8fcd27f + 5aa4780 commit 582f43d

6 files changed

Lines changed: 261 additions & 38 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
1616
- Settings file saves are now atomic to prevent corruption from interrupted writes
1717
### Fixed
1818
- Fixed an issue where `Align Your Steps` scheduler and Unet Loader workflows ignored Regional Prompting (and other addon) conditioning modifiers.
19+
- Fixed the Package Manager "Add Package" teaching tip opening inopportunely while packages were still loading or after opening the add-package dialog
1920
- Fixed bold text not rendering in markdown dialogs on Windows 11 due to Avalonia 11.3.x variable font regression with Segoe UI Variable Text
2021
- Fixed Japanese text appearing compressed/squished in markdown dialogs by ensuring the bundled NotoSansJP font is used for CTextBlock rendering
2122
- Fixed ContentDialog title and buttons not using the correct font for Japanese locale (NotoSansJP) when shown as overlay
@@ -29,6 +30,8 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
2930
- Fixed [#1590](https://github.com/LykosAI/StabilityMatrix/issues/1590) - Startup crash when settings file is corrupted. Settings files are now self-healing with automatic recovery from null bytes, truncated JSON, and missing brackets
3031
- Potentially fixed [#1578](https://github.com/LykosAI/StabilityMatrix/issues/1578) - `SocketException: Address already in use` on Linux startup by cleaning stale interprocess socket files and reactivating the existing window
3132
- Fixed [#1397](https://github.com/LykosAI/StabilityMatrix/issues/1397), [#610](https://github.com/LykosAI/StabilityMatrix/issues/610) - duplicate pip package entries in results - thanks to @e-nord!
33+
- Fixed [#1596](https://github.com/LykosAI/StabilityMatrix/issues/1596) - package installs and managed embedded Python startup being poisoned by inherited shell Python activation variables such as `PYTHONHOME`, `PYTHONPATH`, `VIRTUAL_ENV`, and Conda environment variables
34+
- Potentially fixed [#1578](https://github.com/LykosAI/StabilityMatrix/issues/1578) - `SocketException: Address already in use` on Linux startup by cleaning stale interprocess socket files and reactivating the existing window
3235

3336
## v2.15.6
3437
### Added

StabilityMatrix.Avalonia/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public static class Program
6161
public static void Main(string[] args)
6262
{
6363
StartupTimer.Start();
64+
ProcessRunner.SanitizeCurrentProcessPythonEnvironment();
6465

6566
SetDebugBuild();
6667

StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using AsyncAwaitBestPractices;
44
using Avalonia.Controls;
55
using Avalonia.Threading;
6+
using CommunityToolkit.Mvvm.ComponentModel;
67
using CommunityToolkit.Mvvm.Input;
78
using DynamicData;
89
using DynamicData.Binding;
@@ -61,6 +62,9 @@ public partial class MainPackageManagerViewModel : PageViewModelBase
6162
public IObservableCollection<PackageCardViewModel> PackageCards { get; } =
6263
new ObservableCollectionExtended<PackageCardViewModel>();
6364

65+
[ObservableProperty]
66+
private bool isAddPackageTeachingTipOpen;
67+
6468
private DispatcherTimer timer;
6569

6670
public MainPackageManagerViewModel(
@@ -112,11 +116,13 @@ private void OnOneClickInstallFinished(object? sender, bool e)
112116
public void SetPackages(IEnumerable<InstalledPackage> packages)
113117
{
114118
installedPackages.Edit(s => s.Load(packages));
119+
UpdateAddPackageTeachingTip();
115120
}
116121

117122
public void SetUnknownPackages(IEnumerable<InstalledPackage> packages)
118123
{
119124
unknownInstalledPackages.Edit(s => s.Load(packages));
125+
UpdateAddPackageTeachingTip();
120126
}
121127

122128
protected override async Task OnInitialLoadedAsync()
@@ -151,7 +157,9 @@ public override async Task OnLoadedAsync()
151157
if (Design.IsDesignMode || !settingsManager.IsLibraryDirSet)
152158
return;
153159

160+
IsAddPackageTeachingTipOpen = false;
154161
await LoadPackages();
162+
UpdateAddPackageTeachingTip();
155163

156164
timer.Start();
157165
}
@@ -164,6 +172,7 @@ public override void OnUnloaded()
164172

165173
public void ShowInstallDialog(BasePackage? selectedPackage = null)
166174
{
175+
IsAddPackageTeachingTipOpen = false;
167176
NavigateToSubPage(typeof(PackageInstallBrowserViewModel));
168177
}
169178

@@ -237,6 +246,11 @@ private void NavigateToSubPage(Type viewModelType)
237246
);
238247
}
239248

249+
private void UpdateAddPackageTeachingTip()
250+
{
251+
IsAddPackageTeachingTipOpen = Packages.Count == 0;
252+
}
253+
240254
private void OnInstalledPackagesChanged(object? sender, EventArgs e) =>
241-
OnLoadedAsync().SafeFireAndForget();
255+
Dispatcher.UIThread.InvokeAsync(OnLoadedAsync).SafeFireAndForget();
242256
}

StabilityMatrix.Avalonia/Views/PackageManager/MainPackageManagerView.axaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@
607607
Name="TeachingTip1"
608608
Title="{x:Static lang:Resources.TeachingTip_AddPackageToGetStarted}"
609609
Grid.Row="0"
610-
IsOpen="{Binding !Packages.Count}"
610+
IsOpen="{Binding IsAddPackageTeachingTipOpen}"
611611
PreferredPlacement="Top"
612612
Target="{Binding #AddPackagesButton}" />
613613

StabilityMatrix.Core/Processes/ProcessRunner.cs

Lines changed: 121 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Diagnostics;
1+
using System.Diagnostics;
22
using System.Text;
33
using NLog;
44
using StabilityMatrix.Core.Exceptions;
@@ -10,6 +10,121 @@ public static class ProcessRunner
1010
{
1111
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
1212

13+
// Managed Python/uv subprocesses should not inherit ambient Python/venv activation state.
14+
private static readonly string[] PythonEnvironmentVariablesToSanitize =
15+
[
16+
"PYTHONHOME",
17+
"PYTHONPATH",
18+
"PYTHONSTARTUP",
19+
"PYTHONUSERBASE",
20+
"PYTHONEXECUTABLE",
21+
"__PYVENV_LAUNCHER__",
22+
"VIRTUAL_ENV",
23+
"CONDA_PREFIX",
24+
"CONDA_DEFAULT_ENV",
25+
];
26+
27+
public static void SanitizeCurrentProcessPythonEnvironment(bool setNoUserSite = true)
28+
{
29+
var removedKeys = new List<string>();
30+
foreach (var key in PythonEnvironmentVariablesToSanitize)
31+
{
32+
if (Environment.GetEnvironmentVariable(key, EnvironmentVariableTarget.Process) is null)
33+
continue;
34+
35+
Environment.SetEnvironmentVariable(key, null, EnvironmentVariableTarget.Process);
36+
removedKeys.Add(key);
37+
}
38+
39+
if (setNoUserSite)
40+
{
41+
Environment.SetEnvironmentVariable("PYTHONNOUSERSITE", "1", EnvironmentVariableTarget.Process);
42+
}
43+
44+
if (removedKeys.Count > 0)
45+
{
46+
Logger.Debug(
47+
"Removed inherited Python environment variables from current process: [{Keys}]",
48+
string.Join(", ", removedKeys)
49+
);
50+
}
51+
}
52+
53+
internal static void PrepareEnvironment(
54+
ProcessStartInfo info,
55+
string fileName,
56+
IEnumerable<KeyValuePair<string, string>>? environmentVariables
57+
)
58+
{
59+
if (UsesPythonRuntime(fileName))
60+
{
61+
var explicitKeys =
62+
environmentVariables?.Select(kvp => kvp.Key).ToHashSet(StringComparer.OrdinalIgnoreCase)
63+
?? [];
64+
65+
var removedKeys = new List<string>();
66+
foreach (var key in PythonEnvironmentVariablesToSanitize)
67+
{
68+
if (explicitKeys.Contains(key))
69+
continue;
70+
71+
if (info.Environment.Remove(key))
72+
{
73+
removedKeys.Add(key);
74+
}
75+
}
76+
77+
if (!explicitKeys.Contains("PYTHONNOUSERSITE"))
78+
{
79+
info.Environment["PYTHONNOUSERSITE"] = "1";
80+
}
81+
82+
if (removedKeys.Count > 0)
83+
{
84+
Logger.Debug(
85+
"Removed inherited Python environment variables: [{Keys}]",
86+
string.Join(", ", removedKeys)
87+
);
88+
}
89+
}
90+
91+
ApplyEnvironmentVariables(info, environmentVariables);
92+
}
93+
94+
internal static bool UsesPythonRuntime(string fileName)
95+
{
96+
var executableName = Path.GetFileNameWithoutExtension(fileName);
97+
if (string.IsNullOrWhiteSpace(executableName))
98+
return false;
99+
100+
return executableName.StartsWith("python", StringComparison.OrdinalIgnoreCase)
101+
|| executableName.StartsWith("pypy", StringComparison.OrdinalIgnoreCase)
102+
|| executableName.StartsWith("pip", StringComparison.OrdinalIgnoreCase)
103+
|| executableName.Equals("uv", StringComparison.OrdinalIgnoreCase)
104+
|| executableName.Equals("uvx", StringComparison.OrdinalIgnoreCase);
105+
}
106+
107+
private static void ApplyEnvironmentVariables(
108+
ProcessStartInfo info,
109+
IEnumerable<KeyValuePair<string, string>>? environmentVariables
110+
)
111+
{
112+
if (environmentVariables is null)
113+
return;
114+
115+
var keys = new List<string>();
116+
foreach (var (key, value) in environmentVariables)
117+
{
118+
info.EnvironmentVariables[key] = value;
119+
keys.Add(key);
120+
}
121+
122+
if (keys.Count > 0)
123+
{
124+
Logger.Debug("Setting environment variables: [{Keys}]", string.Join(", ", keys));
125+
}
126+
}
127+
13128
/// <summary>
14129
/// Opens the given URL in the default browser.
15130
/// </summary>
@@ -169,13 +284,7 @@ public static async Task<string> GetProcessOutputAsync(
169284
CreateNoWindow = true,
170285
};
171286

172-
if (environmentVariables != null)
173-
{
174-
foreach (var (key, value) in environmentVariables)
175-
{
176-
info.EnvironmentVariables[key] = value;
177-
}
178-
}
287+
PrepareEnvironment(info, fileName, environmentVariables);
179288

180289
if (workingDirectory != null)
181290
{
@@ -218,13 +327,7 @@ public static async Task<ProcessResult> GetProcessResultAsync(
218327
info.StandardErrorEncoding = Encoding.UTF8;
219328
}
220329

221-
if (environmentVariables != null)
222-
{
223-
foreach (var (key, value) in environmentVariables)
224-
{
225-
info.EnvironmentVariables[key] = value;
226-
}
227-
}
330+
PrepareEnvironment(info, fileName, environmentVariables);
228331

229332
if (workingDirectory != null)
230333
{
@@ -297,13 +400,7 @@ public static async Task<ProcessResult> GetAnsiProcessResultAsync(
297400
CreateNoWindow = true,
298401
};
299402

300-
if (environmentVariables != null)
301-
{
302-
foreach (var (key, value) in environmentVariables)
303-
{
304-
info.EnvironmentVariables[key] = value;
305-
}
306-
}
403+
PrepareEnvironment(info, fileName, environmentVariables);
307404

308405
if (workingDirectory != null)
309406
{
@@ -420,13 +517,7 @@ public static Process StartProcess(
420517
CreateNoWindow = true,
421518
};
422519

423-
if (environmentVariables != null)
424-
{
425-
foreach (var (key, value) in environmentVariables)
426-
{
427-
info.EnvironmentVariables[key] = value;
428-
}
429-
}
520+
PrepareEnvironment(info, fileName, environmentVariables);
430521

431522
if (workingDirectory != null)
432523
{
@@ -519,13 +610,7 @@ public static async Task<ProcessResult> RunBashCommand(
519610
WorkingDirectory = workingDirectory,
520611
};
521612

522-
if (environmentVariables != null)
523-
{
524-
foreach (var (key, value) in environmentVariables)
525-
{
526-
processInfo.EnvironmentVariables[key] = value;
527-
}
528-
}
613+
ApplyEnvironmentVariables(processInfo, environmentVariables);
529614

530615
using var process = new Process();
531616
process.StartInfo = processInfo;

0 commit comments

Comments
 (0)