Skip to content

Commit 0589160

Browse files
authored
Merge pull request LykosAI#1199 from ionite34/more-neo-fixes
Update uv to v0.9.30 and adjust Python version requirements for Forge Neo
2 parents 978b481 + f30eb4d commit 0589160

7 files changed

Lines changed: 251 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
2222
### Changed
2323
- Disabled update checking for legacy InvokeAI installations using Python 3.10.11
2424
- Hide rating stars in the Civitai browser page if no rating is available
25-
- Updated uv to v0.9.26
25+
- Updated uv to v0.9.30
2626
- Updated PortableGit to v2.52.0.windows.1
2727
- Updated Sage/Triton/Nunchaku installers to use GitHub API to fetch latest releases
2828
- Updated ComfyUI installations and updates to automatically install ComfyUI Manager
@@ -32,10 +32,10 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
3232
### Fixed
3333
- Fixed parsing of escape sequences in Inference such as `\\`
3434
- Fixed batch notification firing when only one image is generated
35+
- Fixed [#1541](https://github.com/LykosAI/StabilityMatrix/issues/1541), [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things
3536
- Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - "Selected commit is null" error when installing packages and rate limited by GitHub
3637
- Fixed [#1525](https://github.com/LykosAI/StabilityMatrix/issues/1525) - Crash after downloading a model
3738
- Fixed [#1523](https://github.com/LykosAI/StabilityMatrix/issues/1523), [#1499](https://github.com/LykosAI/StabilityMatrix/issues/1499), [#1494](https://github.com/LykosAI/StabilityMatrix/issues/1494) - Automatic1111 using old stable diffusion repo
38-
- Fixed [#1518](https://github.com/LykosAI/StabilityMatrix/issues/1518), [#1513](https://github.com/LykosAI/StabilityMatrix/issues/1513), [#1488](https://github.com/LykosAI/StabilityMatrix/issues/1488) - Forge Neo update breaking things
3939
- Fixed [#1505](https://github.com/LykosAI/StabilityMatrix/issues/1505) - incorrect port argument for Wan2GP
4040
- Possibly fix [#1502](https://github.com/LykosAI/StabilityMatrix/issues/1502) - English fonts not displaying correctly on Linux in Chinese environments
4141
- Fixed [#1476](https://github.com/LykosAI/StabilityMatrix/issues/1476) - Incorrect shared output folder for Forge Classic/Neo

StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ IPyInstallationManager pyInstallationManager
3535
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
3636

3737
private const string UvMacDownloadUrl =
38-
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-aarch64-apple-darwin.tar.gz";
38+
"https://github.com/astral-sh/uv/releases/download/0.9.30/uv-aarch64-apple-darwin.tar.gz";
3939
private const string UvLinuxDownloadUrl =
40-
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-unknown-linux-gnu.tar.gz";
40+
"https://github.com/astral-sh/uv/releases/download/0.9.30/uv-x86_64-unknown-linux-gnu.tar.gz";
4141

4242
private DirectoryPath HomeDir => settingsManager.LibraryDir;
4343
private DirectoryPath AssetsDir => HomeDir.JoinDir("Assets");
@@ -75,7 +75,7 @@ private bool IsPythonVersionInstalled(PyVersion version) =>
7575
// Cached store of whether or not git is installed
7676
private bool? isGitInstalled;
7777

78-
private string ExpectedUvVersion => "0.9.26";
78+
private string ExpectedUvVersion => "0.9.30";
7979

8080
public bool IsVcBuildToolsInstalled => false;
8181
public bool IsHipSdkInstalled => false;

StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ IPyInstallationManager pyInstallationManager
5050
private const string PythonLibsDownloadUrl = "https://cdn.lykos.ai/python_libs_for_sage.zip";
5151

5252
private const string UvWindowsDownloadUrl =
53-
"https://github.com/astral-sh/uv/releases/download/0.9.26/uv-x86_64-pc-windows-msvc.zip";
53+
"https://github.com/astral-sh/uv/releases/download/0.9.30/uv-x86_64-pc-windows-msvc.zip";
5454

5555
private string HomeDir => settingsManager.LibraryDir;
5656

@@ -116,7 +116,7 @@ private string GetPythonLibraryZipPath(PyVersion version) =>
116116
private string UvExtractPath => Path.Combine(AssetsDir, "uv");
117117
public string UvExePath => Path.Combine(UvExtractPath, "uv.exe");
118118
public bool IsUvInstalled => File.Exists(UvExePath);
119-
private string ExpectedUvVersion => "0.9.26";
119+
private string ExpectedUvVersion => "0.9.30";
120120

121121
// FFmpeg paths
122122
private string FfmpegDownloadPath => Path.Combine(AssetsDir, "ffmpeg.zip");

StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,9 @@ public async Task Update()
442442
return;
443443
}
444444

445+
if (!await ShowPythonUpgradeDialogIfNeeded(basePackage, Package))
446+
return;
447+
445448
var packageName = Package.DisplayName ?? Package.PackageName ?? "";
446449

447450
Text = $"Updating {packageName}";
@@ -591,6 +594,9 @@ private async Task ChangeVersion()
591594
return;
592595
}
593596

597+
if (!await ShowPythonUpgradeDialogIfNeeded(basePackage, Package))
598+
return;
599+
594600
var packageName = Package.DisplayName ?? Package.PackageName ?? "";
595601

596602
Text = $"Updating {packageName}";
@@ -1025,6 +1031,44 @@ private async Task<bool> HasUpdate()
10251031
}
10261032
}
10271033

1034+
private static bool RequiresPythonUpgradeNotice(
1035+
BasePackage basePackage,
1036+
InstalledPackage installedPackage
1037+
)
1038+
{
1039+
if (basePackage.MinimumPythonVersion is not { } minimumVersion)
1040+
return false;
1041+
1042+
return PyVersion.TryParse(installedPackage.PythonVersion, out var currentVersion)
1043+
&& currentVersion < minimumVersion;
1044+
}
1045+
1046+
private async Task<bool> ShowPythonUpgradeDialogIfNeeded(
1047+
BasePackage basePackage,
1048+
InstalledPackage installedPackage
1049+
)
1050+
{
1051+
if (!RequiresPythonUpgradeNotice(basePackage, installedPackage))
1052+
return true;
1053+
1054+
var dialog = new BetterContentDialog
1055+
{
1056+
Title = "Python Upgrade Required",
1057+
Content =
1058+
"This update will recreate the package venv to migrate from Python "
1059+
+ $"{installedPackage.PythonVersion} to {basePackage.MinimumPythonVersion}.\n\n"
1060+
+ "Any custom pip packages manually installed into the current venv may need to be reinstalled. "
1061+
+ "Your launch options, extensions, and generated files are not affected.\n\n"
1062+
+ "You can also install a fresh copy and migrate manually.\n\n"
1063+
+ "Continue with update?",
1064+
PrimaryButtonText = "Continue",
1065+
CloseButtonText = Resources.Action_Cancel,
1066+
DefaultButton = ContentDialogButton.Primary,
1067+
};
1068+
1069+
return await dialog.ShowAsync() == ContentDialogResult.Primary;
1070+
}
1071+
10281072
public void ToggleSharedModelSymlink() => IsSharedModelSymlink = !IsSharedModelSymlink;
10291073

10301074
public void ToggleSharedModelConfig() => IsSharedModelConfig = !IsSharedModelConfig;

StabilityMatrix.Core/Models/Packages/BasePackage.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ public abstract class BasePackage(ISettingsManager settingsManager)
6262
public virtual string? AdminRequiredReason => null;
6363
public virtual PyVersion RecommendedPythonVersion => PyInstallationManager.Python_3_10_17;
6464

65+
/// <summary>
66+
/// Minimum Python version required for updates. When set, updating a package with a lower
67+
/// installed Python version will prompt for venv recreation. Null means no minimum enforced.
68+
/// </summary>
69+
public virtual PyVersion? MinimumPythonVersion => null;
70+
6571
/// <summary>
6672
/// Returns a list of extra commands that can be executed for this package.
6773
/// The function takes an InstalledPackage parameter to operate on a specific installation.

StabilityMatrix.Core/Models/Packages/ForgeClassic.cs

Lines changed: 192 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Injectio.Attributes;
2+
using NLog;
23
using StabilityMatrix.Core.Helper;
34
using StabilityMatrix.Core.Helper.Cache;
45
using StabilityMatrix.Core.Helper.HardwareInfo;
@@ -29,6 +30,11 @@ IPipWheelService pipWheelService
2930
pipWheelService
3031
)
3132
{
33+
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
34+
private const string LegacyUpgradeAlert = "You are updating from an old version";
35+
private const string ContinuePrompt = "Press Enter to Continue";
36+
public override PyVersion? MinimumPythonVersion => Python.PyInstallationManager.Python_3_13_12;
37+
3238
public override string Name => "forge-classic";
3339
public override string Author => "Haoming02";
3440
public override string RepositoryName => "sd-webui-forge-classic";
@@ -43,7 +49,7 @@ IPipWheelService pipWheelService
4349
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.ReallyRecommended;
4450
public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Cuda];
4551
public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();
46-
public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_11_13;
52+
public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_13_12;
4753
public override PackageType PackageType => PackageType.Legacy;
4854

4955
public override Dictionary<SharedOutputType, IReadOnlyList<string>> SharedOutputFolders =>
@@ -183,10 +189,33 @@ public override async Task InstallPackage(
183189
CancellationToken cancellationToken = default
184190
)
185191
{
192+
var requestedPythonVersion =
193+
options.PythonOptions.PythonVersion
194+
?? (
195+
PyVersion.TryParse(installedPackage.PythonVersion, out var parsedVersion)
196+
? parsedVersion
197+
: RecommendedPythonVersion
198+
);
199+
200+
var shouldUpgradePython = options.IsUpdate && requestedPythonVersion < MinimumPythonVersion;
201+
var targetPythonVersion = shouldUpgradePython ? MinimumPythonVersion!.Value : requestedPythonVersion;
202+
203+
if (shouldUpgradePython)
204+
{
205+
onConsoleOutput?.Invoke(
206+
ProcessOutput.FromStdOutLine(
207+
$"Upgrading venv Python from {requestedPythonVersion} to {targetPythonVersion}"
208+
)
209+
);
210+
211+
ResetVenvForPythonUpgrade(installLocation, onConsoleOutput);
212+
}
213+
186214
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
187215
await using var venvRunner = await SetupVenvPure(
188216
installLocation,
189-
pythonVersion: options.PythonOptions.PythonVersion
217+
forceRecreate: shouldUpgradePython,
218+
pythonVersion: targetPythonVersion
190219
)
191220
.ConfigureAwait(false);
192221

@@ -209,18 +238,174 @@ public override async Task InstallPackage(
209238

210239
// Run their install script with our venv Python
211240
venvRunner.WorkingDirectory = new DirectoryPath(installLocation);
212-
venvRunner.RunDetached([.. launchArgs], onConsoleOutput);
213241

214-
await venvRunner.Process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
242+
var sawLegacyUpdatePrompt = false;
243+
244+
var exitCode = await RunInstallScriptWithPromptHandling(
245+
venvRunner,
246+
launchArgs,
247+
onConsoleOutput,
248+
cancellationToken,
249+
onLegacyPromptDetected: () => sawLegacyUpdatePrompt = true
250+
)
251+
.ConfigureAwait(false);
252+
253+
// If legacy prompt was detected, back up old config files regardless of exit code.
254+
if (options.IsUpdate && sawLegacyUpdatePrompt)
255+
{
256+
BackupLegacyConfigFiles(installLocation, onConsoleOutput);
257+
258+
// If it also failed, retry once after the backup.
259+
if (exitCode != 0)
260+
{
261+
onConsoleOutput?.Invoke(
262+
ProcessOutput.FromStdOutLine(
263+
"[ForgeClassic] Retrying install after backing up legacy config files..."
264+
)
265+
);
266+
267+
exitCode = await RunInstallScriptWithPromptHandling(
268+
venvRunner,
269+
launchArgs,
270+
onConsoleOutput,
271+
cancellationToken
272+
)
273+
.ConfigureAwait(false);
274+
}
275+
}
276+
277+
if (exitCode != 0)
278+
{
279+
throw new InvalidOperationException($"Install script failed with exit code {exitCode}");
280+
}
281+
282+
if (
283+
!string.Equals(
284+
installedPackage.PythonVersion,
285+
targetPythonVersion.StringValue,
286+
StringComparison.Ordinal
287+
)
288+
)
289+
{
290+
installedPackage.PythonVersion = targetPythonVersion.StringValue;
291+
}
292+
293+
progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false));
294+
}
295+
296+
private async Task<int> RunInstallScriptWithPromptHandling(
297+
IPyVenvRunner venvRunner,
298+
IReadOnlyCollection<string> launchArgs,
299+
Action<ProcessOutput>? onConsoleOutput,
300+
CancellationToken cancellationToken,
301+
Action? onLegacyPromptDetected = null
302+
)
303+
{
304+
var enterSent = false;
215305

216-
if (venvRunner.Process.ExitCode != 0)
306+
void HandleInstallOutput(ProcessOutput output)
217307
{
308+
onConsoleOutput?.Invoke(output);
309+
310+
var isLegacyPrompt =
311+
output.Text.Contains(LegacyUpgradeAlert, StringComparison.OrdinalIgnoreCase)
312+
|| output.Text.Contains(ContinuePrompt, StringComparison.OrdinalIgnoreCase);
313+
314+
if (!isLegacyPrompt)
315+
return;
316+
317+
onLegacyPromptDetected?.Invoke();
318+
319+
if (enterSent || venvRunner.Process is null || venvRunner.Process.HasExited)
320+
return;
321+
322+
try
323+
{
324+
venvRunner.Process.StandardInput.WriteLine();
325+
enterSent = true;
326+
327+
onConsoleOutput?.Invoke(
328+
ProcessOutput.FromStdOutLine(
329+
"[ForgeClassic] Detected legacy update prompt. Sent Enter automatically."
330+
)
331+
);
332+
}
333+
catch (Exception e)
334+
{
335+
Logger.Warn(e, "Failed to auto-submit Enter for Forge Classic update prompt");
336+
}
337+
}
338+
339+
venvRunner.RunDetached([.. launchArgs], HandleInstallOutput);
340+
var process =
341+
venvRunner.Process
342+
?? throw new InvalidOperationException("Failed to start Forge Classic install process");
343+
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
344+
return process.ExitCode;
345+
}
346+
347+
private void ResetVenvForPythonUpgrade(string installLocation, Action<ProcessOutput>? onConsoleOutput)
348+
{
349+
var venvPath = Path.Combine(installLocation, "venv");
350+
if (!Directory.Exists(venvPath))
351+
return;
352+
353+
try
354+
{
355+
Directory.Delete(venvPath, recursive: true);
356+
onConsoleOutput?.Invoke(
357+
ProcessOutput.FromStdOutLine("[ForgeClassic] Removed existing venv before Python upgrade.")
358+
);
359+
}
360+
catch (Exception e)
361+
{
362+
Logger.Warn(e, "Failed to remove existing venv during Forge Classic Python upgrade");
218363
throw new InvalidOperationException(
219-
$"Install script failed with exit code {venvRunner.Process.ExitCode}"
364+
"Failed to remove existing venv for Python upgrade. Ensure Forge is not running and retry.",
365+
e
220366
);
221367
}
368+
}
222369

223-
progress?.Report(new ProgressReport(1f, "Install complete", isIndeterminate: false));
370+
private void BackupLegacyConfigFiles(string installLocation, Action<ProcessOutput>? onConsoleOutput)
371+
{
372+
BackupLegacyConfigFile(installLocation, "config.json", onConsoleOutput);
373+
BackupLegacyConfigFile(installLocation, "ui-config.json", onConsoleOutput);
374+
}
375+
376+
private void BackupLegacyConfigFile(
377+
string installLocation,
378+
string fileName,
379+
Action<ProcessOutput>? onConsoleOutput
380+
)
381+
{
382+
var sourcePath = Path.Combine(installLocation, fileName);
383+
if (!File.Exists(sourcePath))
384+
return;
385+
386+
var backupPath = GetBackupPath(sourcePath);
387+
File.Move(sourcePath, backupPath);
388+
389+
var message = $"[ForgeClassic] Backed up {fileName} to {Path.GetFileName(backupPath)}";
390+
Logger.Info(message);
391+
onConsoleOutput?.Invoke(ProcessOutput.FromStdOutLine(message));
392+
}
393+
394+
private static string GetBackupPath(string sourcePath)
395+
{
396+
var nextPath = sourcePath + ".bak";
397+
if (!File.Exists(nextPath))
398+
return nextPath;
399+
400+
var index = 1;
401+
while (true)
402+
{
403+
nextPath = sourcePath + $".bak.{index}";
404+
if (!File.Exists(nextPath))
405+
return nextPath;
406+
407+
index++;
408+
}
224409
}
225410

226411
private async Task InstallTritonAndSageAttention(InstalledPackage? installedPackage)

StabilityMatrix.Core/Python/PyInstallationManager.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class PyInstallationManager(IUvManager uvManager, ISettingsManager settin
1919
public static readonly PyVersion Python_3_10_17 = new(3, 10, 17);
2020
public static readonly PyVersion Python_3_11_13 = new(3, 11, 13);
2121
public static readonly PyVersion Python_3_12_10 = new(3, 12, 10);
22+
public static readonly PyVersion Python_3_13_12 = new(3, 13, 12);
2223

2324
/// <summary>
2425
/// List of preferred/target Python versions StabilityMatrix officially supports.
@@ -107,7 +108,7 @@ public async Task<IReadOnlyList<UvPythonInfo>> GetAllAvailablePythonsAsync()
107108
var allPythons = await uvManager.ListAvailablePythonsAsync().ConfigureAwait(false);
108109
Func<UvPythonInfo, bool> isSupportedVersion = settingsManager.Settings.ShowAllAvailablePythonVersions
109110
? p => p is { Source: "cpython", Version.Minor: >= 10 }
110-
: p => p is { Source: "cpython", Version.Minor: >= 10 and <= 12 };
111+
: p => p is { Source: "cpython", Version.Minor: >= 10 and <= 13, Variant: not "freethreaded" };
111112

112113
var filteredPythons = allPythons
113114
.Where(isSupportedVersion)

0 commit comments

Comments
 (0)