Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions docs/PACKAGE_MANAGERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,18 @@ Install Python, which includes pip:
- **macOS:** `brew install python`, or download it from <https://www.python.org/downloads/macos/>
- **Linux:** use your distribution's packages, for example `python3-pip` on Ubuntu.

On Windows, UniGetUI runs `python -m pip` with the first `python` it finds on `PATH`. If typing
`python` in a terminal opens the Microsoft Store or reports that the command is not found,
UniGetUI cannot use it either. Python's documentation covers this case: open Start, search for
**Manage app execution aliases**, and check the Python entries there
(<https://docs.python.org/3/using/windows.html>).

On macOS and Linux, UniGetUI uses `pip3` or `pip` on `PATH` when one is there, and otherwise runs
`python -m pip`, or `python3 -m pip` when there is no `python`.
UniGetUI always runs `python -m pip` with the Python it selects. On Windows it looks for `python`
on `PATH`, then for Pythons installed in Program Files, `%LOCALAPPDATA%\Programs\Python` and the
Python install manager's `%LOCALAPPDATA%\Python`. On macOS and Linux it looks for `python3`, then
`python`, on `PATH`. If typing `python` in a Windows terminal opens the Microsoft Store or reports
that the command is not found, UniGetUI ranks that alias below any installed Python. Python's
documentation covers this case: open Start, search for **Manage app execution aliases**, and check
the Python entries there (<https://docs.python.org/3/using/windows.html>).

Virtual environments, conda environments and embedded Pythons usually belong to another app, so
UniGetUI does not manage them unless you check them under **Python environments** in the Pip
settings. Checking one that is not managed by default requires the administrator setting that
allows changing package manager executables.

Check: `python -m pip --version` (or `python3 -m pip --version`)

Expand Down
13 changes: 12 additions & 1 deletion src/Languages/lang_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2261,5 +2261,16 @@
"WinGet is not available on this system": "WinGet is not available on this system",
"Kill the processes of any search, package listing or maintenance command {0} is still running": "Kill the processes of any search, package listing or maintenance command {0} is still running",
"{0} is still loading.": "{0} is still loading.",
"No package manager matches your search": "No package manager matches your search"
"No package manager matches your search": "No package manager matches your search",
"UniGetUI only manages the Python environments checked below. Virtual environments, conda environments and embedded Pythons usually belong to other apps, so they are not managed unless you check them.": "UniGetUI only manages the Python environments checked below. Virtual environments, conda environments and embedded Pythons usually belong to other apps, so they are not managed unless you check them.",
"No Python interpreters were found": "No Python interpreters were found",
"Python environments": "Python environments",
"Virtual environment": "Virtual environment",
"Conda environment": "Conda environment",
"Embedded Python": "Embedded Python",
"Microsoft Store alias": "Microsoft Store alias",
"System Python": "System Python",
"In use": "In use",
"Manage the Python environment at {0}": "Manage the Python environment at {0}",
"Unrecognized Python": "Unrecognized Python"
}
6 changes: 6 additions & 0 deletions src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,12 @@ public void ReloadPagesWithoutReadyManager()
InstalledPage.ViewModel.ReloadIfManagerBecameReady();
}

public void ReloadPagesAfterManagerExecutableChanged()
{
_ = UpdatesPage.ViewModel.ReloadAfterManagerExecutableChangedAsync();
_ = InstalledPage.ViewModel.ReloadAfterManagerExecutableChangedAsync();
}

private async Task OpenManagerSettingsAsync(IPackageManager? manager)
{
if (!await NavigateToAsync(PageType.Managers))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,24 @@ public async Task ReloadManager()
{
IsLoading = true;
RefreshState();
string previousExecutable = GetEffectiveExecutable();
ApplyProbe(await Task.Run(() =>
{
Manager.Initialize();
return ProbeExecutables();
}));
IsLoading = false;
RefreshState(showVersion: true);
(MainWindow.Instance?.DataContext as MainWindowViewModel)?.ReloadPagesWithoutReadyManager();
var mainWindow = MainWindow.Instance?.DataContext as MainWindowViewModel;
if (GetEffectiveExecutable() != previousExecutable)
mainWindow?.ReloadPagesAfterManagerExecutableChanged();
else
mainWindow?.ReloadPagesWithoutReadyManager();
}

private string GetEffectiveExecutable() =>
Manager.IsReady() ? $"{Manager.Status.ExecutablePath} {Manager.Status.ExecutableCallArgs}" : "";

[RelayCommand]
public void ShowVersion() => RefreshState(showVersion: true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1269,7 +1269,7 @@
private bool IsWithoutReadyManager => LoadsOnStart && _lastLoadHadNoManagerReady;

[RelayCommand]
private void OpenManagerSettings()

Check warning on line 1272 in src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'OpenManagerSettings' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)
{
if (MainWindow.Instance?.DataContext is MainWindowViewModel mainVm)
mainVm.OpenManagerSettings();
Expand All @@ -1285,6 +1285,19 @@
_ = LoadPackages(ReloadReason.Automated);
}

public async Task ReloadAfterManagerExecutableChangedAsync()
{
if (!LoadsOnStart)
return;

if (Loader.IsLoading)
await Loader.WaitForCurrentLoadAsync();
else if (Loader.HasPendingInitialLoad)
return;

await Loader.ReloadPackages();
}

// ─── Sort commands ────────────────────────────────────────────────────────
[RelayCommand] private void SortByName() => SortFieldIndex = 0;
[RelayCommand] private void SortById() => SortFieldIndex = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using UniGetUI.Interface.Enums;
using UniGetUI.PackageEngine.Interfaces;
using UniGetUI.PackageEngine.ManagerClasses.Manager;
using UniGetUI.PackageEngine.Managers.PipManager;
using UniGetUI.PackageEngine.Managers.VcpkgManager;
using CoreSettings = UniGetUI.Core.SettingsEngine.Settings;
using CornerRadius = global::Avalonia.CornerRadius;
Expand Down Expand Up @@ -102,17 +103,8 @@ private void BuildPage()
string savedPath = CoreSettings.GetDictionaryItem<string, string>(CoreSettings.K.ManagerPaths, manager.Name) ?? "";
if (!string.IsNullOrEmpty(savedPath) && File.Exists(savedPath))
AddExecutablePathItem(execCombo, savedPath);
if (string.IsNullOrEmpty(savedPath))
{
var (found, path) = manager.GetExecutableFile();
savedPath = found ? path : "";
}
else if (!File.Exists(savedPath))
{
var (found, path) = manager.GetExecutableFile();
savedPath = found ? path : "";
}
execCombo.SelectedItem = savedPath;
var (executableFound, executablePath) = manager.GetExecutableFile();
execCombo.SelectedItem = executableFound ? executablePath : "";
execCombo.IsEnabled = customPathsAllowed;
execCombo.SelectionChanged += (s, _) =>
{
Expand Down Expand Up @@ -155,26 +147,9 @@ private void BuildPage()

if (!customPathsAllowed)
{
var securityWarning = new TextBlock
{
Text = CoreTools.Translate("For security reasons, changing the executable file is disabled by default"),
FontSize = 12,
FontWeight = FontWeight.SemiBold,
Opacity = 0.7,
TextWrapping = TextWrapping.Wrap,
Classes = { "setting-warning-text" },
};
var (securityWarning, goToSecureBtn) = BuildCustomPathsWarning();
Grid.SetRow(securityWarning, 2);
execGrid.Children.Add(securityWarning);

var goToSecureBtn = new Button
{
Content = new TextBlock { Text = CoreTools.Translate("Change this"), FontSize = 12, Classes = { "hyperlink" } },
Background = Brushes.Transparent,
BorderThickness = new Thickness(0),
Padding = new Thickness(0),
};
goToSecureBtn.Click += (_, _) => ViewModel.NavigateToAdministratorCommand.Execute(null);
Grid.SetRow(goToSecureBtn, 2);
Grid.SetColumn(goToSecureBtn, 1);
execGrid.Children.Add(goToSecureBtn);
Expand Down Expand Up @@ -591,6 +566,13 @@ private void BuildExtraControls(CheckboxCard_Dict disableNotifsCard)
});
break;

case "Pip":
disableNotifsCard.CornerRadius = new CornerRadius(8, 8, 0, 0);
disableNotifsCard.BorderThickness = new Thickness(1, 1, 1, 0);
ExtraControls.Children.Add(disableNotifsCard);
ExtraControls.Children.Add(BuildPythonEnvironmentsCard());
break;

case "vcpkg":
disableNotifsCard.CornerRadius = new CornerRadius(8, 8, 0, 0);
disableNotifsCard.BorderThickness = new Thickness(1, 1, 1, 0);
Expand Down Expand Up @@ -618,6 +600,148 @@ private void BuildExtraControls(CheckboxCard_Dict disableNotifsCard)
}
}

private SettingsCard BuildPythonEnvironmentsCard()
{
var rows = new StackPanel { Spacing = 8, Margin = new Thickness(0, 8, 0, 0) };
var description = new StackPanel { Spacing = 4 };
description.Children.Add(new TextBlock
{
Text = CoreTools.Translate("UniGetUI only manages the Python environments checked below. Virtual environments, conda environments and embedded Pythons usually belong to other apps, so they are not managed unless you check them."),
FontSize = 12,
Opacity = 0.7,
TextWrapping = TextWrapping.Wrap,
});
description.Children.Add(rows);

if (!Pip.CanOptInEnvironments())
{
var gate = new Grid { ColumnDefinitions = new ColumnDefinitions("*,Auto") };
var (warning, change) = BuildCustomPathsWarning();
gate.Children.Add(warning);
Grid.SetColumn(change, 1);
gate.Children.Add(change);
description.Children.Add(gate);
}

void Fill()
{
rows.Children.Clear();
List<string> interpreters = [];
string savedPath = CoreSettings.GetDictionaryItem<string, string>(CoreSettings.K.ManagerPaths, ViewModel.Manager.Name) ?? "";
foreach (string path in ViewModel.CandidateExecutables.Append(savedPath).Append(ViewModel.Manager.Status.ExecutablePath))
{
if (path.Length > 0 && File.Exists(path) && !interpreters.Any(known => Pip.IsSameEnvironment(known, path)))
interpreters.Add(path);
}

if (interpreters.Count == 0)
{
rows.Children.Add(new TextBlock
{
Text = CoreTools.Translate("No Python interpreters were found"),
FontSize = 12,
});
return;
}

foreach (string path in interpreters)
rows.Children.Add(BuildPythonEnvironmentRow(path));
}

Fill();
rows.IsEnabled = !ViewModel.IsLoading;
ViewModel.PropertyChanged += (_, e) =>
{
if (e.PropertyName != nameof(PackageManagerViewModel.IsLoading))
return;

rows.IsEnabled = !ViewModel.IsLoading;
if (!ViewModel.IsLoading)
Fill();
};

return new SettingsCard
{
Header = CoreTools.Translate("Python environments"),
Description = description,
CornerRadius = new CornerRadius(0, 0, 8, 8),
BorderThickness = new Thickness(1, 0, 1, 1),
};
}

private Grid BuildPythonEnvironmentRow(string path)
{
PythonEnvironmentKind kind = Pip.ClassifyEnvironment(path);
string kindText = kind switch
{
PythonEnvironmentKind.VirtualEnvironment => CoreTools.Translate("Virtual environment"),
PythonEnvironmentKind.CondaEnvironment => CoreTools.Translate("Conda environment"),
PythonEnvironmentKind.Embedded => CoreTools.Translate("Embedded Python"),
PythonEnvironmentKind.StoreAlias => CoreTools.Translate("Microsoft Store alias"),
PythonEnvironmentKind.Unknown => CoreTools.Translate("Unrecognized Python"),
_ => CoreTools.Translate("System Python"),
};
if (Pip.IsSameEnvironment(ViewModel.Manager.Status.ExecutablePath, path))
kindText += " · " + CoreTools.Translate("In use");

var row = new Grid { ColumnDefinitions = new ColumnDefinitions("*,Auto") };
var labels = new StackPanel { Spacing = 2 };
labels.Children.Add(new TextBlock
{
Text = path,
FontFamily = new FontFamily("Consolas,Cascadia Mono,Menlo,monospace"),
TextWrapping = TextWrapping.Wrap,
});
labels.Children.Add(new TextBlock
{
Text = kindText,
FontSize = 12,
Opacity = 0.7,
});
row.Children.Add(labels);

var manage = new CheckBox
{
Content = CoreTools.Translate("Manage"),
IsChecked = Pip.IsEnvironmentManaged(path, kind)
|| (ViewModel.Manager is Pip pip && pip.IsChosenExecutable(path)),
VerticalAlignment = VerticalAlignment.Center,
Margin = new Thickness(8, 0, 0, 0),
};
manage.IsEnabled = Pip.CanOptInEnvironments() || Pip.IsManagedByDefault(kind) || manage.IsChecked == true;
AutomationProperties.SetName(manage, CoreTools.Translate("Manage the Python environment at {0}", path));
manage.IsCheckedChanged += (_, _) =>
{
Pip.SetEnvironmentManaged(path, manage.IsChecked == true);
_ = ViewModel.ReloadManagerCommand.ExecuteAsync(null);
Comment thread
GabrielDuf marked this conversation as resolved.
Resolved
};
Grid.SetColumn(manage, 1);
row.Children.Add(manage);
return row;
}

private (TextBlock Warning, Button Change) BuildCustomPathsWarning()
{
var warning = new TextBlock
{
Text = CoreTools.Translate("For security reasons, changing the executable file is disabled by default"),
FontSize = 12,
FontWeight = FontWeight.SemiBold,
Opacity = 0.7,
TextWrapping = TextWrapping.Wrap,
Classes = { "setting-warning-text" },
};
var change = new Button
{
Content = new TextBlock { Text = CoreTools.Translate("Change this"), FontSize = 12, Classes = { "hyperlink" } },
Background = Brushes.Transparent,
BorderThickness = new Thickness(0),
Padding = new Thickness(0),
};
change.Click += (_, _) => ViewModel.NavigateToAdministratorCommand.Execute(null);
return (warning, change);
}

private ButtonCard BuildVcpkgRootCard()
{
var vcpkgRootCard = new ButtonCard
Expand Down
2 changes: 2 additions & 0 deletions src/UniGetUI.Core.Settings/SettingsEngine_Names.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public enum K
TuiTheme,
BunPreferLatestVersions,
DisablePipHttpUpdateCheck,
PipEnvironmentOverrides,
RedactUsernameInLog,
DisableReleaseNotesOnUpdate,
LastKnownBuildNumber,
Expand Down Expand Up @@ -297,6 +298,7 @@ public static string ResolveKey(K key)
K.TuiTheme => "TuiTheme",
K.BunPreferLatestVersions => "BunPreferLatestVersions",
K.DisablePipHttpUpdateCheck => "DisablePipHttpUpdateCheck",
K.PipEnvironmentOverrides => "PipEnvironmentOverrides",
K.RedactUsernameInLog => "RedactUsernameInLog",
K.DisableReleaseNotesOnUpdate => "DisableReleaseNotesOnUpdate",
K.LastKnownBuildNumber => "LastKnownBuildNumber",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,12 @@ protected override IReadOnlyList<Uri> GetScreenshots_UnSafe(IPackage package)

protected override string? GetInstallLocation_UnSafe(IPackage package)
{
var full_path = Path.Join(
Path.GetDirectoryName(Manager.Status.ExecutablePath),
"Lib",
"site-packages",
package.Id
return Pip.ResolveInstallLocation(
Manager.Status.Version,
Manager.Status.ExecutablePath,
package.Id,
Directory.Exists
);
return Directory.Exists(full_path) ? full_path : Path.GetDirectoryName(full_path);
}

protected override IReadOnlyList<string> GetInstallableVersions_UnSafe(IPackage package)
Expand Down
Loading
Loading