Browse Source

Merge pull request #333 from LykosAI/main

Release v2.7.2
pull/336/head
JT 11 months ago committed by GitHub
parent
commit
f3766a6b08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      CHANGELOG.md
  2. 18
      StabilityMatrix.Avalonia/Helpers/UriHandler.cs
  3. 8
      StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs
  4. 2
      StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs
  5. 40
      StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
  6. 28
      StabilityMatrix.Core/Models/FileInterfaces/FileSystemPath.cs
  7. 92
      StabilityMatrix.Core/Models/Packages/A3WebUI.cs
  8. 232
      StabilityMatrix.Core/Models/Packages/ComfyUI.cs
  9. 46
      StabilityMatrix.Core/Models/Packages/Fooocus.cs
  10. 97
      StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs
  11. 61
      StabilityMatrix.Core/Processes/ProcessArgsBuilder.cs
  12. 33
      StabilityMatrix.Core/Python/PipInstallArgs.cs
  13. 87
      StabilityMatrix.Core/Python/PipPackageSpecifier.cs

11
CHANGELOG.md

@ -5,6 +5,17 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
## v2.7.2
### Changed
- Changed Symlink shared folder link targets for Automatic1111 and ComfyUI. From `ControlNet -> models/controlnet` to `ControlNet -> models/controlnet/ControlNet` and `T2IAdapter -> models/controlnet/T2IAdapter`.
- Changed FreeU defaults to match recommended SD1.5 defaults
- Changed default denoise strength from 1.0 to 0.7
### Fixed
- Fixed ControlNet / T2IAdapter shared folder links for Automatic1111 conflicting with each other
- Fixed URIScheme registration errors on Linux
- Fixed RuinedFooocus missing output folder on startup
- Fixed incorrect Fooocus VRAM launch arguments
## v2.7.1 ## v2.7.1
### Added ### Added
- Added Turkish UI language option, thanks to Progesor for the translation - Added Turkish UI language option, thanks to Progesor for the translation

18
StabilityMatrix.Avalonia/Helpers/UriHandler.cs

@ -56,8 +56,6 @@ public class UriHandler
Environment.Exit(0); Environment.Exit(0);
} }
public void Callback() { }
public void RegisterUriScheme() public void RegisterUriScheme()
{ {
if (Compat.IsWindows) if (Compat.IsWindows)
@ -65,9 +63,19 @@ public class UriHandler
RegisterUriSchemeWin(); RegisterUriSchemeWin();
} }
else else
{
// Try to register on unix but ignore errors
// Library does not support some distros
try
{ {
RegisterUriSchemeUnix(); RegisterUriSchemeUnix();
} }
catch (Exception e)
{
Debug.WriteLine(e);
Console.WriteLine(e);
}
}
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
@ -92,11 +100,7 @@ public class UriHandler
private void RegisterUriSchemeUnix() private void RegisterUriSchemeUnix()
{ {
var service = URISchemeServiceFactory.GetURISchemeSerivce( var service = URISchemeServiceFactory.GetURISchemeSerivce(Scheme, Description, Compat.AppCurrentPath.FullPath);
Scheme,
Description,
Compat.AppCurrentPath.FullPath
);
service.Set(); service.Set();
} }
} }

8
StabilityMatrix.Avalonia/ViewModels/Inference/FreeUCardViewModel.cs

@ -14,21 +14,25 @@ public partial class FreeUCardViewModel : LoadableViewModelBase
public const string ModuleKey = "FreeU"; public const string ModuleKey = "FreeU";
[ObservableProperty] [ObservableProperty]
[NotifyDataErrorInfo]
[Required] [Required]
[Range(0D, 10D)] [Range(0D, 10D)]
private double b1 = 1.1; private double b1 = 1.5;
[ObservableProperty] [ObservableProperty]
[NotifyDataErrorInfo]
[Required] [Required]
[Range(0D, 10D)] [Range(0D, 10D)]
private double b2 = 1.2; private double b2 = 1.6;
[ObservableProperty] [ObservableProperty]
[NotifyDataErrorInfo]
[Required] [Required]
[Range(0D, 10D)] [Range(0D, 10D)]
private double s1 = 0.9; private double s1 = 0.9;
[ObservableProperty] [ObservableProperty]
[NotifyDataErrorInfo]
[Required] [Required]
[Range(0D, 10D)] [Range(0D, 10D)]
private double s2 = 0.2; private double s2 = 0.2;

2
StabilityMatrix.Avalonia/ViewModels/Inference/SamplerCardViewModel.cs

@ -42,7 +42,7 @@ public partial class SamplerCardViewModel : LoadableViewModelBase, IParametersLo
private bool isDenoiseStrengthEnabled; private bool isDenoiseStrengthEnabled;
[ObservableProperty] [ObservableProperty]
private double denoiseStrength = 1; private double denoiseStrength = 0.7f;
[ObservableProperty] [ObservableProperty]
[property: Category("Settings")] [property: Category("Settings")]

40
StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs

@ -89,9 +89,7 @@ public partial class MainWindow : AppWindowBase
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
navigationService.SetFrame( navigationService.SetFrame(FrameView ?? throw new NullReferenceException("Frame not found"));
FrameView ?? throw new NullReferenceException("Frame not found")
);
} }
protected override void OnOpened(EventArgs e) protected override void OnOpened(EventArgs e)
@ -134,14 +132,13 @@ public partial class MainWindow : AppWindowBase
return; return;
// Navigate to first page // Navigate to first page
Dispatcher.UIThread.Post( Dispatcher
.UIThread
.Post(
() => () =>
navigationService.NavigateTo( navigationService.NavigateTo(
vm.Pages[0], vm.Pages[0],
new BetterSlideNavigationTransition new BetterSlideNavigationTransition { Effect = SlideNavigationTransitionEffect.FromBottom }
{
Effect = SlideNavigationTransitionEffect.FromBottom
}
) )
); );
@ -167,18 +164,19 @@ public partial class MainWindow : AppWindowBase
{ {
var mainViewModel = (MainWindowViewModel)DataContext!; var mainViewModel = (MainWindowViewModel)DataContext!;
mainViewModel.SelectedCategory = mainViewModel.Pages mainViewModel.SelectedCategory = mainViewModel
.Pages
.Concat(mainViewModel.FooterPages) .Concat(mainViewModel.FooterPages)
.FirstOrDefault(x => x.GetType() == e.ViewModelType); .FirstOrDefault(x => x.GetType() == e.ViewModelType);
} }
private void OnUpdateAvailable(object? sender, UpdateInfo? updateInfo) private void OnUpdateAvailable(object? sender, UpdateInfo? updateInfo)
{ {
Dispatcher.UIThread.Post(() => Dispatcher
.UIThread
.Post(() =>
{ {
var vm = DataContext as MainWindowViewModel; if (DataContext is MainWindowViewModel vm && vm.ShouldShowUpdateAvailableTeachingTip(updateInfo))
if (vm!.ShouldShowUpdateAvailableTeachingTip(updateInfo))
{ {
var target = this.FindControl<NavigationViewItem>("FooterUpdateItem")!; var target = this.FindControl<NavigationViewItem>("FooterUpdateItem")!;
var tip = this.FindControl<TeachingTip>("UpdateAvailableTeachingTip")!; var tip = this.FindControl<TeachingTip>("UpdateAvailableTeachingTip")!;
@ -284,7 +282,9 @@ public partial class MainWindow : AppWindowBase
private void OnImageLoadFailed(object? sender, ImageLoadFailedEventArgs e) private void OnImageLoadFailed(object? sender, ImageLoadFailedEventArgs e)
{ {
Dispatcher.UIThread.Post(() => Dispatcher
.UIThread
.Post(() =>
{ {
var fileName = Path.GetFileName(e.Url); var fileName = Path.GetFileName(e.Url);
var displayName = string.IsNullOrEmpty(fileName) ? e.Url : fileName; var displayName = string.IsNullOrEmpty(fileName) ? e.Url : fileName;
@ -308,11 +308,7 @@ public partial class MainWindow : AppWindowBase
if (ActualThemeVariant == ThemeVariant.Dark) if (ActualThemeVariant == ThemeVariant.Dark)
{ {
var color = this.TryFindResource( var color = this.TryFindResource("SolidBackgroundFillColorBase", ThemeVariant.Dark, out var value)
"SolidBackgroundFillColorBase",
ThemeVariant.Dark,
out var value
)
? (Color2)(Color)value! ? (Color2)(Color)value!
: new Color2(30, 31, 34); : new Color2(30, 31, 34);
@ -323,11 +319,7 @@ public partial class MainWindow : AppWindowBase
else if (ActualThemeVariant == ThemeVariant.Light) else if (ActualThemeVariant == ThemeVariant.Light)
{ {
// Similar effect here // Similar effect here
var color = this.TryFindResource( var color = this.TryFindResource("SolidBackgroundFillColorBase", ThemeVariant.Light, out var value)
"SolidBackgroundFillColorBase",
ThemeVariant.Light,
out var value
)
? (Color2)(Color)value! ? (Color2)(Color)value!
: new Color2(243, 243, 243); : new Color2(243, 243, 243);

28
StabilityMatrix.Core/Models/FileInterfaces/FileSystemPath.cs

@ -1,6 +1,6 @@
namespace StabilityMatrix.Core.Models.FileInterfaces; namespace StabilityMatrix.Core.Models.FileInterfaces;
public class FileSystemPath : IEquatable<FileSystemPath>, IEquatable<string> public class FileSystemPath : IEquatable<FileSystemPath>, IEquatable<string>, IFormattable
{ {
public string FullPath { get; } public string FullPath { get; }
@ -9,23 +9,38 @@ public class FileSystemPath : IEquatable<FileSystemPath>, IEquatable<string>
FullPath = path; FullPath = path;
} }
protected FileSystemPath(FileSystemPath path) : this(path.FullPath) protected FileSystemPath(FileSystemPath path)
: this(path.FullPath) { }
protected FileSystemPath(params string[] paths)
: this(Path.Combine(paths)) { }
public override string ToString()
{ {
return FullPath;
} }
protected FileSystemPath(params string[] paths) : this(Path.Combine(paths)) /// <inheritdoc />
string IFormattable.ToString(string? format, IFormatProvider? formatProvider)
{ {
return ToString(format, formatProvider);
} }
public override string ToString() /// <summary>
/// Overridable IFormattable.ToString method.
/// By default, returns <see cref="FullPath"/>.
/// </summary>
protected virtual string ToString(string? format, IFormatProvider? formatProvider)
{ {
return FullPath; return FullPath;
} }
public bool Equals(FileSystemPath? other) public bool Equals(FileSystemPath? other)
{ {
if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(null, other))
if (ReferenceEquals(this, other)) return true; return false;
if (ReferenceEquals(this, other))
return true;
return FullPath == other.FullPath; return FullPath == other.FullPath;
} }
@ -51,5 +66,6 @@ public class FileSystemPath : IEquatable<FileSystemPath>, IEquatable<string>
// Implicit conversions to and from string // Implicit conversions to and from string
public static implicit operator string(FileSystemPath path) => path.FullPath; public static implicit operator string(FileSystemPath path) => path.FullPath;
public static implicit operator FileSystemPath(string path) => new(path); public static implicit operator FileSystemPath(string path) => new(path);
} }

92
StabilityMatrix.Core/Models/Packages/A3WebUI.cs

@ -56,12 +56,14 @@ public class A3WebUI(
[SharedFolderType.Karlo] = new[] { "models/karlo" }, [SharedFolderType.Karlo] = new[] { "models/karlo" },
[SharedFolderType.TextualInversion] = new[] { "embeddings" }, [SharedFolderType.TextualInversion] = new[] { "embeddings" },
[SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" },
[SharedFolderType.ControlNet] = new[] { "models/ControlNet" }, [SharedFolderType.ControlNet] = new[] { "models/controlnet/ControlNet" },
[SharedFolderType.Codeformer] = new[] { "models/Codeformer" }, [SharedFolderType.Codeformer] = new[] { "models/Codeformer" },
[SharedFolderType.LDSR] = new[] { "models/LDSR" }, [SharedFolderType.LDSR] = new[] { "models/LDSR" },
[SharedFolderType.AfterDetailer] = new[] { "models/adetailer" }, [SharedFolderType.AfterDetailer] = new[] { "models/adetailer" },
[SharedFolderType.T2IAdapter] = new[] { "models/controlnet" }, [SharedFolderType.T2IAdapter] = new[] { "models/controlnet/T2IAdapter" },
[SharedFolderType.IpAdapter] = new[] { "models/ipadapter" } [SharedFolderType.IpAdapter] = new[] { "models/controlnet/IpAdapter" },
[SharedFolderType.InvokeIpAdapters15] = new[] { "models/controlnet/DiffusersIpAdapters" },
[SharedFolderType.InvokeIpAdaptersXl] = new[] { "models/controlnet/DiffusersIpAdaptersXL" }
}; };
public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders => public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders =>
@ -183,39 +185,44 @@ public class A3WebUI(
) )
{ {
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true)); progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
// Setup venv
await using var venvRunner = new PyVenvRunner(Path.Combine(installLocation, "venv"));
venvRunner.WorkingDirectory = installLocation;
await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false);
switch (torchVersion) var venvRunner = await SetupVenv(installLocation, forceRecreate: true).ConfigureAwait(false);
await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true));
var requirements = new FilePath(installLocation, "requirements_versions.txt");
var pipArgs = new PipInstallArgs()
.WithTorch("==2.0.1")
.WithTorchVision("==0.15.2")
.WithTorchExtraIndex(
torchVersion switch
{ {
case TorchVersion.Cpu: TorchVersion.Cpu => "cpu",
await InstallCpuTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); TorchVersion.Cuda => "cu118",
break; TorchVersion.Rocm => "rocm5.1.1",
case TorchVersion.Cuda: _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null)
await InstallCudaTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false);
break;
case TorchVersion.Rocm:
await InstallRocmTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false);
break;
default:
throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null);
} }
)
.WithParsedFromRequirementsTxt(
await requirements.ReadAllTextAsync().ConfigureAwait(false),
excludePattern: "torch"
);
if (torchVersion == TorchVersion.Cuda)
{
pipArgs = pipArgs.WithXFormers("==0.0.20");
}
// v1.6.0 needs a httpx qualifier to fix a gradio issue
if (versionOptions.VersionTag?.Contains("1.6.0") ?? false) if (versionOptions.VersionTag?.Contains("1.6.0") ?? false)
{ {
await venvRunner.PipInstall("httpx==0.24.1", onConsoleOutput); pipArgs = pipArgs.AddArg("httpx==0.24.1");
} }
// Install requirements file await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing Package Requirements", isIndeterminate: true));
Logger.Info("Installing requirements_versions.txt");
var requirements = new FilePath(installLocation, "requirements_versions.txt");
await venvRunner
.PipInstallFromRequirements(requirements, onConsoleOutput, excludes: "torch")
.ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Updating configuration", isIndeterminate: true)); progress?.Report(new ProgressReport(-1f, "Updating configuration", isIndeterminate: true));
@ -261,21 +268,22 @@ public class A3WebUI(
VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, OnExit); VenvRunner.RunDetached(args.TrimEnd(), HandleConsoleOutput, OnExit);
} }
private async Task InstallRocmTorch( /// <inheritdoc />
PyVenvRunner venvRunner, public override async Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod)
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{ {
progress?.Report(new ProgressReport(-1f, "Installing PyTorch for ROCm", isIndeterminate: true)); // Migration for `controlnet` -> `controlnet/ControlNet` and `controlnet/T2IAdapter`
// If the original link exists, delete it first
await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false); if (installDirectory.JoinDir("models/controlnet") is { IsSymbolicLink: true } controlnetOldLink)
{
Logger.Info("Migration: Removing old controlnet link {Path}", controlnetOldLink);
await controlnetOldLink.DeleteAsync(false).ConfigureAwait(false);
}
await venvRunner // Resume base setup
.PipInstall( await base.SetupModelFolders(installDirectory, sharedFolderMethod).ConfigureAwait(false);
new PipInstallArgs().WithTorch("==2.0.1").WithTorchVision().WithTorchExtraIndex("rocm5.1.1"),
onConsoleOutput
)
.ConfigureAwait(false);
} }
/// <inheritdoc />
public override Task UpdateModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) =>
SetupModelFolders(installDirectory, sharedFolderMethod);
} }

232
StabilityMatrix.Core/Models/Packages/ComfyUI.cs

@ -10,6 +10,7 @@ using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services; using StabilityMatrix.Core.Services;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel; using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NamingConventions;
@ -53,12 +54,12 @@ public class ComfyUI(
[SharedFolderType.TextualInversion] = new[] { "models/embeddings" }, [SharedFolderType.TextualInversion] = new[] { "models/embeddings" },
[SharedFolderType.VAE] = new[] { "models/vae" }, [SharedFolderType.VAE] = new[] { "models/vae" },
[SharedFolderType.ApproxVAE] = new[] { "models/vae_approx" }, [SharedFolderType.ApproxVAE] = new[] { "models/vae_approx" },
[SharedFolderType.ControlNet] = new[] { "models/controlnet" }, [SharedFolderType.ControlNet] = new[] { "models/controlnet/ControlNet" },
[SharedFolderType.GLIGEN] = new[] { "models/gligen" }, [SharedFolderType.GLIGEN] = new[] { "models/gligen" },
[SharedFolderType.ESRGAN] = new[] { "models/upscale_models" }, [SharedFolderType.ESRGAN] = new[] { "models/upscale_models" },
[SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" }, [SharedFolderType.Hypernetwork] = new[] { "models/hypernetworks" },
[SharedFolderType.IpAdapter] = new[] { "models/ipadapter" }, [SharedFolderType.IpAdapter] = new[] { "models/ipadapter" },
[SharedFolderType.T2IAdapter] = new[] { "models/controlnet" }, [SharedFolderType.T2IAdapter] = new[] { "models/controlnet/T2IAdapter" },
}; };
public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders => public override Dictionary<SharedOutputType, IReadOnlyList<string>>? SharedOutputFolders =>
@ -155,80 +156,48 @@ public class ComfyUI(
venvRunner.WorkingDirectory = installLocation; venvRunner.WorkingDirectory = installLocation;
await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false); await venvRunner.Setup(true, onConsoleOutput).ConfigureAwait(false);
// Install torch / xformers based on gpu info await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
switch (torchVersion)
progress?.Report(new ProgressReport(-1f, "Installing Package Requirements...", isIndeterminate: true));
var pipArgs = new PipInstallArgs();
pipArgs = torchVersion switch
{ {
case TorchVersion.Cpu: TorchVersion.DirectMl => pipArgs.WithTorchDirectML(),
await InstallCpuTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false); TorchVersion.Mps
break; => pipArgs.AddArg("--pre").WithTorch().WithTorchVision().WithTorchExtraIndex("nightly/cpu"),
case TorchVersion.Cuda: _
await venvRunner => pipArgs
.PipInstall(
new PipInstallArgs()
.WithTorch("~=2.1.0")
.WithTorchVision()
.WithXFormers("==0.0.22.post4")
.AddArg("--upgrade") .AddArg("--upgrade")
.WithTorchExtraIndex("cu121"), .WithTorch("~=2.1.0")
onConsoleOutput
)
.ConfigureAwait(false);
break;
case TorchVersion.DirectMl:
await venvRunner
.PipInstall(new PipInstallArgs().WithTorchDirectML(), onConsoleOutput)
.ConfigureAwait(false);
break;
case TorchVersion.Rocm:
await InstallRocmTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false);
break;
case TorchVersion.Mps:
await venvRunner
.PipInstall(
new PipInstallArgs()
.AddArg("--pre")
.WithTorch()
.WithTorchVision() .WithTorchVision()
.WithTorchExtraIndex("nightly/cpu"), .WithTorchExtraIndex(
onConsoleOutput torchVersion switch
{
TorchVersion.Cpu => "cpu",
TorchVersion.Cuda => "cu121",
TorchVersion.Rocm => "rocm5.6",
_ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null)
}
) )
.ConfigureAwait(false); };
break;
default: if (torchVersion == TorchVersion.Cuda)
throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null); {
pipArgs = pipArgs.WithXFormers("==0.0.22.post4");
} }
// Install requirements file (skip torch) var requirements = new FilePath(installLocation, "requirements.txt");
progress?.Report(new ProgressReport(-1, "Installing Package Requirements", isIndeterminate: true));
var requirementsFile = new FilePath(installLocation, "requirements.txt"); pipArgs = pipArgs.WithParsedFromRequirementsTxt(
await requirements.ReadAllTextAsync().ConfigureAwait(false),
excludePattern: "torch"
);
await venvRunner await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
.PipInstallFromRequirements(requirementsFile, onConsoleOutput, excludes: "torch")
.ConfigureAwait(false);
progress?.Report(new ProgressReport(1, "Installing Package Requirements", isIndeterminate: false)); progress?.Report(new ProgressReport(1, "Installed Package Requirements", isIndeterminate: false));
}
private async Task AutoDetectAndInstallTorch(PyVenvRunner venvRunner, IProgress<ProgressReport>? progress = null)
{
var gpus = HardwareHelper.IterGpuInfo().ToList();
if (gpus.Any(g => g.IsNvidia))
{
await InstallCudaTorch(venvRunner, progress).ConfigureAwait(false);
}
else if (HardwareHelper.PreferRocm())
{
await InstallRocmTorch(venvRunner, progress).ConfigureAwait(false);
}
else if (HardwareHelper.PreferDirectML())
{
await InstallDirectMlTorch(venvRunner, progress).ConfigureAwait(false);
}
else
{
await InstallCpuTorch(venvRunner, progress).ConfigureAwait(false);
}
} }
public override async Task RunPackage( public override async Task RunPackage(
@ -267,26 +236,55 @@ public class ComfyUI(
} }
} }
public override Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) public override Task SetupModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) =>
sharedFolderMethod switch
{
SharedFolderMethod.Symlink => SetupModelFoldersSymlink(installDirectory),
SharedFolderMethod.Configuration => SetupModelFoldersConfig(installDirectory),
SharedFolderMethod.None => Task.CompletedTask,
_ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null)
};
public override Task UpdateModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) =>
SetupModelFolders(installDirectory, sharedFolderMethod);
public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod)
{
return sharedFolderMethod switch
{
SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod),
SharedFolderMethod.Configuration => RemoveConfigSection(installDirectory),
SharedFolderMethod.None => Task.CompletedTask,
_ => throw new ArgumentOutOfRangeException(nameof(sharedFolderMethod), sharedFolderMethod, null)
};
}
private async Task SetupModelFoldersSymlink(DirectoryPath installDirectory)
{ {
switch (sharedFolderMethod) // Migration for `controlnet` -> `controlnet/ControlNet` and `controlnet/T2IAdapter`
// If the original link exists, delete it first
if (installDirectory.JoinDir("models/controlnet") is { IsSymbolicLink: true } controlnetOldLink)
{ {
case SharedFolderMethod.None: Logger.Info("Migration: Removing old controlnet link {Path}", controlnetOldLink);
return Task.CompletedTask; await controlnetOldLink.DeleteAsync(false).ConfigureAwait(false);
case SharedFolderMethod.Symlink: }
return base.SetupModelFolders(installDirectory, sharedFolderMethod);
// Resume base setup
await base.SetupModelFolders(installDirectory, SharedFolderMethod.Symlink).ConfigureAwait(false);
} }
var extraPathsYamlPath = installDirectory + "extra_model_paths.yaml"; private async Task SetupModelFoldersConfig(DirectoryPath installDirectory)
{
var extraPathsYamlPath = installDirectory.JoinFile("extra_model_paths.yaml");
var modelsDir = SettingsManager.ModelsDirectory; var modelsDir = SettingsManager.ModelsDirectory;
var exists = File.Exists(extraPathsYamlPath); if (!extraPathsYamlPath.Exists)
if (!exists)
{ {
Logger.Info("Creating extra_model_paths.yaml"); Logger.Info("Creating extra_model_paths.yaml");
File.WriteAllText(extraPathsYamlPath, string.Empty); extraPathsYamlPath.Create();
} }
var yaml = File.ReadAllText(extraPathsYamlPath);
var yaml = await extraPathsYamlPath.ReadAllTextAsync().ConfigureAwait(false);
using var sr = new StringReader(yaml); using var sr = new StringReader(yaml);
var yamlStream = new YamlStream(); var yamlStream = new YamlStream();
yamlStream.Load(sr); yamlStream.Load(sr);
@ -307,7 +305,7 @@ public class ComfyUI(
if (stabilityMatrixNode.Key != null) if (stabilityMatrixNode.Key != null)
{ {
if (stabilityMatrixNode.Value is not YamlMappingNode nodeValue) if (stabilityMatrixNode.Value is not YamlMappingNode nodeValue)
return Task.CompletedTask; return;
nodeValue.Children["checkpoints"] = Path.Combine(modelsDir, "StableDiffusion"); nodeValue.Children["checkpoints"] = Path.Combine(modelsDir, "StableDiffusion");
nodeValue.Children["vae"] = Path.Combine(modelsDir, "VAE"); nodeValue.Children["vae"] = Path.Combine(modelsDir, "VAE");
@ -319,7 +317,11 @@ public class ComfyUI(
+ $"{Path.Combine(modelsDir, "SwinIR")}"; + $"{Path.Combine(modelsDir, "SwinIR")}";
nodeValue.Children["embeddings"] = Path.Combine(modelsDir, "TextualInversion"); nodeValue.Children["embeddings"] = Path.Combine(modelsDir, "TextualInversion");
nodeValue.Children["hypernetworks"] = Path.Combine(modelsDir, "Hypernetwork"); nodeValue.Children["hypernetworks"] = Path.Combine(modelsDir, "Hypernetwork");
nodeValue.Children["controlnet"] = Path.Combine(modelsDir, "ControlNet"); nodeValue.Children["controlnet"] = string.Join(
'\n',
Path.Combine(modelsDir, "ControlNet"),
Path.Combine(modelsDir, "T2IAdapter")
);
nodeValue.Children["clip"] = Path.Combine(modelsDir, "CLIP"); nodeValue.Children["clip"] = Path.Combine(modelsDir, "CLIP");
nodeValue.Children["diffusers"] = Path.Combine(modelsDir, "Diffusers"); nodeValue.Children["diffusers"] = Path.Combine(modelsDir, "Diffusers");
nodeValue.Children["gligen"] = Path.Combine(modelsDir, "GLIGEN"); nodeValue.Children["gligen"] = Path.Combine(modelsDir, "GLIGEN");
@ -340,7 +342,10 @@ public class ComfyUI(
}, },
{ "embeddings", Path.Combine(modelsDir, "TextualInversion") }, { "embeddings", Path.Combine(modelsDir, "TextualInversion") },
{ "hypernetworks", Path.Combine(modelsDir, "Hypernetwork") }, { "hypernetworks", Path.Combine(modelsDir, "Hypernetwork") },
{ "controlnet", Path.Combine(modelsDir, "ControlNet") }, {
"controlnet",
string.Join('\n', Path.Combine(modelsDir, "ControlNet"), Path.Combine(modelsDir, "T2IAdapter"))
},
{ "clip", Path.Combine(modelsDir, "CLIP") }, { "clip", Path.Combine(modelsDir, "CLIP") },
{ "diffusers", Path.Combine(modelsDir, "Diffusers") }, { "diffusers", Path.Combine(modelsDir, "Diffusers") },
{ "gligen", Path.Combine(modelsDir, "GLIGEN") }, { "gligen", Path.Combine(modelsDir, "GLIGEN") },
@ -357,82 +362,45 @@ public class ComfyUI(
newRootNode.Children.Add(stabilityMatrixNode); newRootNode.Children.Add(stabilityMatrixNode);
var serializer = new SerializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build(); var serializer = new SerializerBuilder()
var yamlData = serializer.Serialize(newRootNode); .WithNamingConvention(UnderscoredNamingConvention.Instance)
File.WriteAllText(extraPathsYamlPath, yamlData); .WithDefaultScalarStyle(ScalarStyle.Literal)
.Build();
return Task.CompletedTask; var yamlData = serializer.Serialize(newRootNode);
await extraPathsYamlPath.WriteAllTextAsync(yamlData).ConfigureAwait(false);
} }
public override Task UpdateModelFolders(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) => private static async Task RemoveConfigSection(DirectoryPath installDirectory)
sharedFolderMethod switch
{ {
SharedFolderMethod.Symlink => base.UpdateModelFolders(installDirectory, sharedFolderMethod), var extraPathsYamlPath = installDirectory.JoinFile("extra_model_paths.yaml");
SharedFolderMethod.Configuration => SetupModelFolders(installDirectory, sharedFolderMethod),
SharedFolderMethod.None => Task.CompletedTask,
_ => Task.CompletedTask
};
public override Task RemoveModelFolderLinks(DirectoryPath installDirectory, SharedFolderMethod sharedFolderMethod) if (!extraPathsYamlPath.Exists)
{ {
return sharedFolderMethod switch return;
{
SharedFolderMethod.Configuration => RemoveConfigSection(installDirectory),
SharedFolderMethod.None => Task.CompletedTask,
SharedFolderMethod.Symlink => base.RemoveModelFolderLinks(installDirectory, sharedFolderMethod),
_ => Task.CompletedTask
};
}
private Task RemoveConfigSection(string installDirectory)
{
var extraPathsYamlPath = Path.Combine(installDirectory, "extra_model_paths.yaml");
var exists = File.Exists(extraPathsYamlPath);
if (!exists)
{
return Task.CompletedTask;
} }
var yaml = File.ReadAllText(extraPathsYamlPath); var yaml = await extraPathsYamlPath.ReadAllTextAsync().ConfigureAwait(false);
using var sr = new StringReader(yaml); using var sr = new StringReader(yaml);
var yamlStream = new YamlStream(); var yamlStream = new YamlStream();
yamlStream.Load(sr); yamlStream.Load(sr);
if (!yamlStream.Documents.Any()) if (!yamlStream.Documents.Any())
{ {
return Task.CompletedTask; return;
} }
var root = yamlStream.Documents[0].RootNode; var root = yamlStream.Documents[0].RootNode;
if (root is not YamlMappingNode mappingNode) if (root is not YamlMappingNode mappingNode)
{ {
return Task.CompletedTask; return;
} }
mappingNode.Children.Remove("stability_matrix"); mappingNode.Children.Remove("stability_matrix");
var serializer = new SerializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build(); var serializer = new SerializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance).Build();
var yamlData = serializer.Serialize(mappingNode); var yamlData = serializer.Serialize(mappingNode);
File.WriteAllText(extraPathsYamlPath, yamlData);
return Task.CompletedTask; await extraPathsYamlPath.WriteAllTextAsync(yamlData).ConfigureAwait(false);
}
private async Task InstallRocmTorch(
PyVenvRunner venvRunner,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{
progress?.Report(new ProgressReport(-1f, "Installing PyTorch for ROCm", isIndeterminate: true));
await venvRunner.PipInstall("--upgrade pip wheel", onConsoleOutput).ConfigureAwait(false);
await venvRunner
.PipInstall(
new PipInstallArgs().WithTorch("==2.0.1").WithTorchVision().WithTorchExtraIndex("rocm5.6"),
onConsoleOutput
)
.ConfigureAwait(false);
} }
} }

46
StabilityMatrix.Core/Models/Packages/Fooocus.cs

@ -1,4 +1,5 @@
using System.Diagnostics; using System.Collections.Immutable;
using System.Diagnostics;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper;
@ -76,11 +77,11 @@ public class Fooocus(
Type = LaunchOptionType.Bool, Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch
{ {
MemoryLevel.Low => "--lowvram", MemoryLevel.Low => "--always-low-vram",
MemoryLevel.Medium => "--normalvram", MemoryLevel.Medium => "--always-normal-vram",
_ => null _ => null
}, },
Options = { "--highvram", "--normalvram", "--lowvram", "--novram" } Options = { "--always-high-vram", "--always-normal-vram", "--always-low-vram", "--always-no-vram" }
}, },
new LaunchOptionDefinition new LaunchOptionDefinition
{ {
@ -152,39 +153,38 @@ public class Fooocus(
{ {
var venvRunner = await SetupVenv(installLocation, forceRecreate: true).ConfigureAwait(false); var venvRunner = await SetupVenv(installLocation, forceRecreate: true).ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing torch...", isIndeterminate: true)); progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true));
var pipArgs = new PipInstallArgs();
if (torchVersion == TorchVersion.DirectMl) if (torchVersion == TorchVersion.DirectMl)
{ {
await venvRunner pipArgs = pipArgs.WithTorchDirectML();
.PipInstall(new PipInstallArgs().WithTorchDirectML(), onConsoleOutput)
.ConfigureAwait(false);
} }
else else
{ {
var extraIndex = torchVersion switch pipArgs = pipArgs
.WithTorch("==2.1.0")
.WithTorchVision("==0.16.0")
.WithTorchExtraIndex(
torchVersion switch
{ {
TorchVersion.Cpu => "cpu", TorchVersion.Cpu => "cpu",
TorchVersion.Cuda => "cu121", TorchVersion.Cuda => "cu121",
TorchVersion.Rocm => "rocm5.6", TorchVersion.Rocm => "rocm5.6",
_ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null) _ => throw new ArgumentOutOfRangeException(nameof(torchVersion), torchVersion, null)
}; }
);
await venvRunner
.PipInstall(
new PipInstallArgs()
.WithTorch("==2.1.0")
.WithTorchVision("==0.16.0")
.WithTorchExtraIndex(extraIndex),
onConsoleOutput
)
.ConfigureAwait(false);
} }
var requirements = new FilePath(installLocation, "requirements_versions.txt"); var requirements = new FilePath(installLocation, "requirements_versions.txt");
await venvRunner
.PipInstallFromRequirements(requirements, onConsoleOutput, excludes: "torch") pipArgs = pipArgs.WithParsedFromRequirementsTxt(
.ConfigureAwait(false); await requirements.ReadAllTextAsync().ConfigureAwait(false),
excludePattern: "torch"
);
await venvRunner.PipInstall(pipArgs, onConsoleOutput).ConfigureAwait(false);
} }
public override async Task RunPackage( public override async Task RunPackage(

97
StabilityMatrix.Core/Models/Packages/RuinedFooocus.cs

@ -1,9 +1,11 @@
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Helper.Cache;
using StabilityMatrix.Core.Helper.HardwareInfo;
using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes; using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Python;
using StabilityMatrix.Core.Services; using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Core.Models.Packages; namespace StabilityMatrix.Core.Models.Packages;
@ -26,6 +28,79 @@ public class RuinedFooocus(
new("https://raw.githubusercontent.com/runew0lf/pmmconfigs/main/RuinedFooocus_ss.png"); new("https://raw.githubusercontent.com/runew0lf/pmmconfigs/main/RuinedFooocus_ss.png");
public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert; public override PackageDifficulty InstallerSortOrder => PackageDifficulty.Expert;
public override List<LaunchOptionDefinition> LaunchOptions =>
new()
{
new LaunchOptionDefinition
{
Name = "Preset",
Type = LaunchOptionType.Bool,
Options = { "--preset anime", "--preset realistic" }
},
new LaunchOptionDefinition
{
Name = "Port",
Type = LaunchOptionType.String,
Description = "Sets the listen port",
Options = { "--port" }
},
new LaunchOptionDefinition
{
Name = "Share",
Type = LaunchOptionType.Bool,
Description = "Set whether to share on Gradio",
Options = { "--share" }
},
new LaunchOptionDefinition
{
Name = "Listen",
Type = LaunchOptionType.String,
Description = "Set the listen interface",
Options = { "--listen" }
},
new LaunchOptionDefinition
{
Name = "Output Directory",
Type = LaunchOptionType.String,
Description = "Override the output directory",
Options = { "--output-directory" }
},
new()
{
Name = "VRAM",
Type = LaunchOptionType.Bool,
InitialValue = HardwareHelper.IterGpuInfo().Select(gpu => gpu.MemoryLevel).Max() switch
{
MemoryLevel.Low => "--lowvram",
MemoryLevel.Medium => "--normalvram",
_ => null
},
Options = { "--highvram", "--normalvram", "--lowvram", "--novram" }
},
new LaunchOptionDefinition
{
Name = "Use DirectML",
Type = LaunchOptionType.Bool,
Description = "Use pytorch with DirectML support",
InitialValue = HardwareHelper.PreferDirectML(),
Options = { "--directml" }
},
new LaunchOptionDefinition
{
Name = "Disable Xformers",
Type = LaunchOptionType.Bool,
InitialValue = !HardwareHelper.HasNvidiaGpu(),
Options = { "--disable-xformers" }
},
new LaunchOptionDefinition
{
Name = "Auto-Launch",
Type = LaunchOptionType.Bool,
Options = { "--auto-launch" }
},
LaunchOptionDefinition.Extras
};
public override async Task InstallPackage( public override async Task InstallPackage(
string installLocation, string installLocation,
TorchVersion torchVersion, TorchVersion torchVersion,
@ -39,13 +114,23 @@ public class RuinedFooocus(
{ {
var venvRunner = await SetupVenv(installLocation, forceRecreate: true).ConfigureAwait(false); var venvRunner = await SetupVenv(installLocation, forceRecreate: true).ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Installing torch...", isIndeterminate: true)); progress?.Report(new ProgressReport(-1f, "Installing requirements...", isIndeterminate: true));
await InstallCudaTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false);
var requirements = new FilePath(installLocation, "requirements_versions.txt"); var requirements = new FilePath(installLocation, "requirements_versions.txt");
await venvRunner await venvRunner
.PipInstallFromRequirements(requirements, onConsoleOutput, excludes: "torch") .PipInstall(
new PipInstallArgs()
.WithTorch("==2.0.1")
.WithTorchVision("==0.15.2")
.WithXFormers("==0.0.20")
.WithTorchExtraIndex("cu118")
.WithParsedFromRequirementsTxt(
await requirements.ReadAllTextAsync().ConfigureAwait(false),
excludePattern: "torch"
),
onConsoleOutput
)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
else else
@ -60,5 +145,9 @@ public class RuinedFooocus(
) )
.ConfigureAwait(false); .ConfigureAwait(false);
} }
// Create output folder since it's not created by default
var outputFolder = new DirectoryPath(installLocation, OutputFolderName);
outputFolder.Create();
} }
} }

61
StabilityMatrix.Core/Processes/ProcessArgsBuilder.cs

@ -1,6 +1,5 @@
using System.Diagnostics; using System.Collections.Immutable;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using OneOf;
namespace StabilityMatrix.Core.Processes; namespace StabilityMatrix.Core.Processes;
@ -9,14 +8,7 @@ namespace StabilityMatrix.Core.Processes;
/// </summary> /// </summary>
public record ProcessArgsBuilder public record ProcessArgsBuilder
{ {
protected ProcessArgsBuilder() { } public IImmutableList<Argument> Arguments { get; init; } = ImmutableArray<Argument>.Empty;
public ProcessArgsBuilder(params Argument[] arguments)
{
Arguments = arguments.ToList();
}
public List<Argument> Arguments { get; init; } = new();
private IEnumerable<string> ToStringArgs() private IEnumerable<string> ToStringArgs()
{ {
@ -34,6 +26,11 @@ public record ProcessArgsBuilder
} }
} }
public ProcessArgsBuilder(params Argument[] arguments)
{
Arguments = arguments.ToImmutableArray();
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
@ -45,8 +42,7 @@ public record ProcessArgsBuilder
return ToStringArgs().ToArray(); return ToStringArgs().ToArray();
} }
public static implicit operator ProcessArgs(ProcessArgsBuilder builder) => public static implicit operator ProcessArgs(ProcessArgsBuilder builder) => builder.ToProcessArgs();
builder.ToProcessArgs();
} }
public static class ProcessArgBuilderExtensions public static class ProcessArgBuilderExtensions
@ -55,7 +51,33 @@ public static class ProcessArgBuilderExtensions
public static T AddArg<T>(this T builder, Argument argument) public static T AddArg<T>(this T builder, Argument argument)
where T : ProcessArgsBuilder where T : ProcessArgsBuilder
{ {
return builder with { Arguments = builder.Arguments.Append(argument).ToList() }; return builder with { Arguments = builder.Arguments.Add(argument) };
}
[Pure]
public static T AddArgs<T>(this T builder, params Argument[] argument)
where T : ProcessArgsBuilder
{
return builder with { Arguments = builder.Arguments.AddRange(argument) };
}
[Pure]
public static T UpdateArg<T>(this T builder, string key, Argument argument)
where T : ProcessArgsBuilder
{
var oldArg = builder
.Arguments
.FirstOrDefault(x => x.Match(stringArg => stringArg == key, tupleArg => tupleArg.Item1 == key));
if (oldArg is null)
{
return builder.AddArg(argument);
}
return builder with
{
Arguments = builder.Arguments.Replace(oldArg, argument)
};
} }
[Pure] [Pure]
@ -64,15 +86,10 @@ public static class ProcessArgBuilderExtensions
{ {
return builder with return builder with
{ {
Arguments = builder.Arguments Arguments = builder
.Where( .Arguments
x => .Where(x => x.Match(stringArg => stringArg != argumentKey, tupleArg => tupleArg.Item1 != argumentKey))
x.Match( .ToImmutableArray()
stringArg => stringArg != argumentKey,
tupleArg => tupleArg.Item1 != argumentKey
)
)
.ToList()
}; };
} }
} }

33
StabilityMatrix.Core/Python/PipInstallArgs.cs

@ -1,4 +1,7 @@
using StabilityMatrix.Core.Processes; using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Processes;
namespace StabilityMatrix.Core.Python; namespace StabilityMatrix.Core.Python;
@ -9,20 +12,36 @@ public record PipInstallArgs : ProcessArgsBuilder
public PipInstallArgs WithTorch(string version = "") => this.AddArg($"torch{version}"); public PipInstallArgs WithTorch(string version = "") => this.AddArg($"torch{version}");
public PipInstallArgs WithTorchDirectML(string version = "") => public PipInstallArgs WithTorchDirectML(string version = "") => this.AddArg($"torch-directml{version}");
this.AddArg($"torch-directml{version}");
public PipInstallArgs WithTorchVision(string version = "") => public PipInstallArgs WithTorchVision(string version = "") => this.AddArg($"torchvision{version}");
this.AddArg($"torchvision{version}");
public PipInstallArgs WithXFormers(string version = "") => this.AddArg($"xformers{version}"); public PipInstallArgs WithXFormers(string version = "") => this.AddArg($"xformers{version}");
public PipInstallArgs WithExtraIndex(string indexUrl) => public PipInstallArgs WithExtraIndex(string indexUrl) => this.AddArg(("--extra-index-url", indexUrl));
this.AddArg(("--extra-index-url", indexUrl));
public PipInstallArgs WithTorchExtraIndex(string index) => public PipInstallArgs WithTorchExtraIndex(string index) =>
this.AddArg(("--extra-index-url", $"https://download.pytorch.org/whl/{index}")); this.AddArg(("--extra-index-url", $"https://download.pytorch.org/whl/{index}"));
public PipInstallArgs WithParsedFromRequirementsTxt(
string requirements,
[StringSyntax(StringSyntaxAttribute.Regex)] string? excludePattern = null
)
{
var requirementsEntries = requirements
.SplitLines(StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
.AsEnumerable();
if (excludePattern is not null)
{
var excludeRegex = new Regex($"^{excludePattern}$");
requirementsEntries = requirementsEntries.Where(s => !excludeRegex.IsMatch(s));
}
return this.AddArgs(requirementsEntries.Select(s => (Argument)s).ToArray());
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {

87
StabilityMatrix.Core/Python/PipPackageSpecifier.cs

@ -0,0 +1,87 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using StabilityMatrix.Core.Processes;
namespace StabilityMatrix.Core.Python;
public partial record PipPackageSpecifier
{
public required string Name { get; init; }
public string? Constraint { get; init; }
public string? Version { get; init; }
public string? VersionConstraint => Constraint is null || Version is null ? null : Constraint + Name;
public static PipPackageSpecifier Parse(string value)
{
var result = TryParse(value, true, out var packageSpecifier);
Debug.Assert(result);
return packageSpecifier!;
}
public static bool TryParse(string value, [NotNullWhen(true)] out PipPackageSpecifier? packageSpecifier)
{
return TryParse(value, false, out packageSpecifier);
}
private static bool TryParse(
string value,
bool throwOnFailure,
[NotNullWhen(true)] out PipPackageSpecifier? packageSpecifier
)
{
var match = PackageSpecifierRegex().Match(value);
if (!match.Success)
{
if (throwOnFailure)
{
throw new ArgumentException($"Invalid package specifier: {value}");
}
packageSpecifier = null;
return false;
}
packageSpecifier = new PipPackageSpecifier
{
Name = match.Groups["package_name"].Value,
Constraint = match.Groups["version_constraint"].Value,
Version = match.Groups["version"].Value
};
return true;
}
/// <inheritdoc />
public override string ToString()
{
return Name + VersionConstraint;
}
public static implicit operator Argument(PipPackageSpecifier specifier)
{
return specifier.VersionConstraint is null
? new Argument(specifier.Name)
: new Argument((specifier.Name, specifier.VersionConstraint));
}
public static implicit operator PipPackageSpecifier(string specifier)
{
return Parse(specifier);
}
/// <summary>
/// Regex to match a pip package specifier.
/// </summary>
[GeneratedRegex(
"(?<package_name>[a-zA-Z0-9_]+)(?<version_specifier>(?<version_constraint>==|>=|<=|>|<|~=|!=)(<version>[a-zA-Z0-9_.]+))?",
RegexOptions.CultureInvariant,
1000
)]
private static partial Regex PackageSpecifierRegex();
}
Loading…
Cancel
Save