Skip to content

Commit ab0cf39

Browse files
authored
Merge pull request LykosAI#1200 from ionite34/backport/main/pr-1199
[dev to main] backport: Update uv to v0.9.30 and adjust Python version requirements for Forge Neo (1199)
2 parents ca33cf6 + a71504b commit ab0cf39

File tree

7 files changed

+251
-15
lines changed

7 files changed

+251
-15
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
1313
### Changed
1414
- Disabled update checking for legacy InvokeAI installations using Python 3.10.11
1515
- Hide rating stars in the Civitai browser page if no rating is available
16-
- Updated uv to v0.9.26
16+
- Updated uv to v0.9.30
1717
- Updated PortableGit to v2.52.0.windows.1
1818
- Updated Sage/Triton/Nunchaku installers to use GitHub API to fetch latest releases
1919
- Updated ComfyUI installations and updates to automatically install ComfyUI Manager
2020
- Updated gfx110X Windows ROCm nightly index - thanks to @NeuralFault!
2121
- Updated ComfyUI-Zluda install to more closely match the author's intended installation method - thanks to @NeuralFault!
2222
- Updated Forge Classic installs/updates to use the upstream install script for better version compatibility with torch/sage/triton/nunchaku
2323
### Fixed
24+
- 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
2425
- Fixed [#1529](https://github.com/LykosAI/StabilityMatrix/issues/1529) - "Selected commit is null" error when installing packages and rate limited by GitHub
2526
- Fixed [#1525](https://github.com/LykosAI/StabilityMatrix/issues/1525) - Crash after downloading a model
2627
- 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
27-
- 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
2828
- Fixed [#1505](https://github.com/LykosAI/StabilityMatrix/issues/1505) - incorrect port argument for Wan2GP
2929
- Possibly fix [#1502](https://github.com/LykosAI/StabilityMatrix/issues/1502) - English fonts not displaying correctly on Linux in Chinese environments
3030
- 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
public string GitBinPath => Path.Combine(PortableGitInstallDir, "bin");
122122
public bool IsVcBuildToolsInstalled => Directory.Exists(VcBuildToolsExistsPath);

StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs

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

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

446449
Text = $"Updating {packageName}";
@@ -590,6 +593,9 @@ private async Task ChangeVersion()
590593
return;
591594
}
592595

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

595601
Text = $"Updating {packageName}";
@@ -989,6 +995,44 @@ private async Task<bool> HasUpdate()
989995
}
990996
}
991997

998+
private static bool RequiresPythonUpgradeNotice(
999+
BasePackage basePackage,
1000+
InstalledPackage installedPackage
1001+
)
1002+
{
1003+
if (basePackage.MinimumPythonVersion is not { } minimumVersion)
1004+
return false;
1005+
1006+
return PyVersion.TryParse(installedPackage.PythonVersion, out var currentVersion)
1007+
&& currentVersion < minimumVersion;
1008+
}
1009+
1010+
private async Task<bool> ShowPythonUpgradeDialogIfNeeded(
1011+
BasePackage basePackage,
1012+
InstalledPackage installedPackage
1013+
)
1014+
{
1015+
if (!RequiresPythonUpgradeNotice(basePackage, installedPackage))
1016+
return true;
1017+
1018+
var dialog = new BetterContentDialog
1019+
{
1020+
Title = "Python Upgrade Required",
1021+
Content =
1022+
"This update will recreate the package venv to migrate from Python "
1023+
+ $"{installedPackage.PythonVersion} to {basePackage.MinimumPythonVersion}.\n\n"
1024+
+ "Any custom pip packages manually installed into the current venv may need to be reinstalled. "
1025+
+ "Your launch options, extensions, and generated files are not affected.\n\n"
1026+
+ "You can also install a fresh copy and migrate manually.\n\n"
1027+
+ "Continue with update?",
1028+
PrimaryButtonText = "Continue",
1029+
CloseButtonText = Resources.Action_Cancel,
1030+
DefaultButton = ContentDialogButton.Primary,
1031+
};
1032+
1033+
return await dialog.ShowAsync() == ContentDialogResult.Primary;
1034+
}
1035+
9921036
public void ToggleSharedModelSymlink() => IsSharedModelSymlink = !IsSharedModelSymlink;
9931037

9941038
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";
@@ -44,7 +50,7 @@ IPipWheelService pipWheelService
4450
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.ReallyRecommended;
4551
public override IEnumerable<TorchIndex> AvailableTorchIndices => [TorchIndex.Cuda];
4652
public override bool IsCompatible => HardwareHelper.HasNvidiaGpu();
47-
public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_11_13;
53+
public override PyVersion RecommendedPythonVersion => Python.PyInstallationManager.Python_3_13_12;
4854
public override PackageType PackageType => PackageType.Legacy;
4955

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

@@ -210,18 +239,174 @@ public override async Task InstallPackage(
210239

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

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

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

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

227412
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)