Skip to content

Commit fe7ea5d

Browse files
authored
Fix package managers for macOS/Linux compatibility (#4542)
1 parent dabb9cd commit fe7ea5d

11 files changed

Lines changed: 348 additions & 185 deletions

File tree

src/UniGetUI.Avalonia/Views/Pages/SettingsPages/PackageManagerPage.axaml.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ private void BuildExtraControls(CheckboxCard_Dict disableNotifsCard)
221221
{
222222
ExtraControls.Children.Clear();
223223
var manager = ViewModel.Manager;
224-
bool managerHasSources = manager.Capabilities.SupportsCustomSources && manager.Name != "Vcpkg";
224+
bool managerHasSources = manager.Capabilities.SupportsCustomSources && manager.Name != "vcpkg";
225225

226226
if (managerHasSources)
227227
{
@@ -330,7 +330,7 @@ private void BuildExtraControls(CheckboxCard_Dict disableNotifsCard)
330330
ExtraControls.Children.Add(chocoSysChoco);
331331
break;
332332

333-
case "Vcpkg":
333+
case "vcpkg":
334334
disableNotifsCard.CornerRadius = new CornerRadius(8, 8, 0, 0);
335335
disableNotifsCard.BorderThickness = new Thickness(1, 1, 1, 0);
336336
ExtraControls.Children.Add(disableNotifsCard);
@@ -405,7 +405,8 @@ private ButtonCard BuildVcpkgRootCard()
405405
descPanel.Children.Add(openBtn);
406406
vcpkgRootCard.Description = descPanel;
407407

408-
vcpkgRootCard.Click += (_, _) => ViewModel.PickVcpkgRootCommand.Execute(vcpkgRootCard);
408+
vcpkgRootCard.Command = ViewModel.PickVcpkgRootCommand;
409+
vcpkgRootCard.CommandParameter = vcpkgRootCard;
409410
return vcpkgRootCard;
410411
}
411412

src/UniGetUI.PackageEngine.Enums/OverridenInstallationOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public struct OverridenInstallationOptions
66
public bool? RunAsAdministrator;
77
public bool PowerShell_DoNotSetScopeParameter = false;
88
public bool? WinGet_SpecifyVersion = null;
9+
public bool Pip_BreakSystemPackages = false;
910

1011
public OverridenInstallationOptions(string? scope = null, bool? runAsAdministrator = null)
1112
{
@@ -15,6 +16,6 @@ public OverridenInstallationOptions(string? scope = null, bool? runAsAdministrat
1516

1617
public override string ToString()
1718
{
18-
return $"<Scope={Scope};RunAsAdministrator={RunAsAdministrator};WG_SpecifyVersion={WinGet_SpecifyVersion};PS_NoScope={PowerShell_DoNotSetScopeParameter}>";
19+
return $"<Scope={Scope};RunAsAdministrator={RunAsAdministrator};WG_SpecifyVersion={WinGet_SpecifyVersion};PS_NoScope={PowerShell_DoNotSetScopeParameter};Pip_BreakSystemPackages={Pip_BreakSystemPackages}>";
1920
}
2021
}

src/UniGetUI.PackageEngine.Managers.Cargo/Cargo.cs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ namespace UniGetUI.PackageEngine.Managers.CargoManager;
1818

1919
public partial class Cargo : PackageManager
2020
{
21-
[GeneratedRegex(@"(\w+)\s=\s""(\d+\.\d+\.\d+)""\s*#\s(.*)")]
21+
[GeneratedRegex(@"([\w-]+)\s=\s""(\d+\.\d+\.\d+)""\s*#\s(.*)")]
2222
private static partial Regex SearchLineRegex();
2323

2424
[GeneratedRegex(@"(.+)v(\d+\.\d+\.\d+)\s*v(\d+\.\d+\.\d+)\s*(Yes|No)")]
2525
private static partial Regex UpdateLineRegex();
2626

27+
// Matches "ripgrep v15.1.0:" lines from `cargo install --list`
28+
[GeneratedRegex(@"^([\w-]+)\s+v(\d+\.\d+\.\d+):")]
29+
private static partial Regex InstallListLineRegex();
30+
2731
public Cargo()
2832
{
2933
string cargoCommand = OperatingSystem.IsWindows() ? "cargo.exe" : "cargo";
@@ -126,19 +130,19 @@ protected override IReadOnlyList<Package> FindPackages_UnSafe(string query)
126130
package.VersionString
127131
);
128132
if (versionInfo.bin_names?.Length > 0)
129-
{
130133
BinPackages.Add(package);
131-
}
132134
}
133135
catch (Exception ex)
134136
{
135-
logger.AddToStdErr($"{ex.Message}");
137+
// On API failure, include the package rather than silently drop it
138+
logger.AddToStdErr($"bin_names check failed for {package.Id}: {ex.Message}");
139+
BinPackages.Add(package);
136140
}
137141

138142
if (i + 1 == Packages.Count)
139143
break;
140-
// Crates.io api requests that we send no more than one request per second
141-
Task.Delay(Math.Max(0, 1000 - (int)((DateTime.Now - startTime).TotalMilliseconds)))
144+
// Crates.io requires no more than one request per second
145+
Task.Delay(Math.Max(0, 1000 - (int)(DateTime.Now - startTime).TotalMilliseconds))
142146
.GetAwaiter()
143147
.GetResult();
144148
}
@@ -158,6 +162,9 @@ protected override IReadOnlyList<Package> GetInstalledPackages_UnSafe()
158162
return GetPackages(LoggableTaskType.ListInstalledPackages);
159163
}
160164

165+
public readonly bool HasBinstall =
166+
CoreTools.Which(OperatingSystem.IsWindows() ? "cargo-binstall.exe" : "cargo-binstall").Item1;
167+
161168
public override IReadOnlyList<string> FindCandidateExecutableFiles() =>
162169
CoreTools.WhichMultiple(OperatingSystem.IsWindows() ? "cargo.exe" : "cargo");
163170

@@ -183,6 +190,9 @@ protected override void _loadManagerVersion(out string version)
183190
Logger.Error("cargo version error: " + error);
184191
}
185192

193+
public void InvalidateInstalledCache() =>
194+
TaskRecycler<List<Match>>.RemoveFromCache(GetInstalledCommandOutput);
195+
186196
private IReadOnlyList<Package> GetPackages(LoggableTaskType taskType)
187197
{
188198
List<Package> Packages = [];
@@ -214,13 +224,35 @@ private List<Match> GetInstalledCommandOutput()
214224
logger.AddToStdOut(line);
215225
var match = UpdateLineRegex().Match(line);
216226
if (match.Success)
217-
{
218227
output.Add(match);
219-
}
220228
}
221229
logger.AddToStdErr(p.StandardError.ReadToEnd());
222230
p.WaitForExit();
223231
logger.Close(p.ExitCode);
232+
233+
if (output.Count > 0)
234+
return output;
235+
236+
// Fallback: cargo-update is not installed, use the built-in `cargo install --list`.
237+
// No latest-version info is available, so updates won't be detected, but the installed
238+
// packages list will be populated correctly.
239+
using Process fallback = GetProcess(Status.ExecutablePath, "install --list");
240+
IProcessTaskLogger fallbackLogger = TaskLogger.CreateNew(LoggableTaskType.OtherTask, fallback);
241+
fallbackLogger.AddToStdOut("Falling back to `cargo install --list` (cargo-update not available)");
242+
fallback.Start();
243+
while ((line = fallback.StandardOutput.ReadLine()) is not null)
244+
{
245+
fallbackLogger.AddToStdOut(line);
246+
var m = InstallListLineRegex().Match(line);
247+
if (!m.Success) continue;
248+
// Synthesise a match compatible with UpdateLineRegex (same installed and latest version → no update)
249+
var fake = UpdateLineRegex().Match($"{m.Groups[1].Value} v{m.Groups[2].Value} v{m.Groups[2].Value} No");
250+
if (fake.Success)
251+
output.Add(fake);
252+
}
253+
fallbackLogger.AddToStdErr(fallback.StandardError.ReadToEnd());
254+
fallback.WaitForExit();
255+
fallbackLogger.Close(fallback.ExitCode);
224256
return output;
225257
}
226258

src/UniGetUI.PackageEngine.Managers.Cargo/Helpers/CargoPkgOperationHelper.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,23 @@ OperationType operation
1515
{
1616
var installVersion = options.Version == string.Empty ? package.VersionString : options.Version;
1717

18+
bool hasBinstall = ((Cargo)Manager).HasBinstall;
19+
1820
List<string> parameters;
1921
switch (operation)
2022
{
2123
case OperationType.Install:
22-
parameters = [Manager.Properties.InstallVerb, "--version", installVersion, package.Id];
24+
if (hasBinstall)
25+
parameters = [Manager.Properties.InstallVerb, "--version", installVersion, package.Id];
26+
else
27+
parameters = ["install", package.Id, "--version", installVersion];
2328
break;
2429

2530
case OperationType.Update:
26-
parameters = [Manager.Properties.UpdateVerb, package.Id];
31+
if (hasBinstall)
32+
parameters = [Manager.Properties.UpdateVerb, package.Id];
33+
else
34+
parameters = ["install", package.Id, "--force"];
2735
break;
2836

2937
case OperationType.Uninstall:
@@ -36,13 +44,16 @@ OperationType operation
3644

3745
if (operation is OperationType.Install or OperationType.Update)
3846
{
39-
parameters.Add("--no-confirm");
47+
if (hasBinstall)
48+
{
49+
parameters.Add("--no-confirm");
4050

41-
if (options.SkipHashCheck)
42-
parameters.Add("--skip-signatures");
51+
if (options.SkipHashCheck)
52+
parameters.Add("--skip-signatures");
4353

44-
if (options.CustomInstallLocation != "")
45-
parameters.AddRange(["--install-path", options.CustomInstallLocation]);
54+
if (options.CustomInstallLocation != "")
55+
parameters.AddRange(["--install-path", options.CustomInstallLocation]);
56+
}
4657
}
4758

4859
parameters.AddRange(
@@ -64,6 +75,11 @@ protected override OperationVeredict _getOperationResult(
6475
int returnCode
6576
)
6677
{
67-
return returnCode == 0 ? OperationVeredict.Success : OperationVeredict.Failure;
78+
if (returnCode == 0)
79+
{
80+
((Cargo)Manager).InvalidateInstalledCache();
81+
return OperationVeredict.Success;
82+
}
83+
return OperationVeredict.Failure;
6884
}
6985
}

src/UniGetUI.PackageEngine.Managers.Generic.NuGet/BaseNuGet.cs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ namespace UniGetUI.PackageEngine.Managers.PowerShellManager
1414
{
1515
public abstract class BaseNuGet : PackageManager
1616
{
17+
/// <summary>
18+
/// When true, searches use Packages()?$filter=substringof(query,Id) which searches by
19+
/// package name only but returns reliable results (e.g. PSGallery's Search() endpoint
20+
/// silently omits some packages). When false, the standard Search() endpoint is used
21+
/// which supports full-text search across name, description, and tags.
22+
/// </summary>
23+
protected virtual bool UseSubstringSearch => false;
1724
public static Dictionary<long, string> Manifests = new();
1825

1926
public sealed override void Initialize()
@@ -71,17 +78,26 @@ protected sealed override IReadOnlyList<Package> FindPackages_UnSafe(string quer
7178
{
7279
try
7380
{
74-
Uri? SearchUrl = new(
75-
$"{source.Url}/Search()"
76-
+ $"?$filter=IsLatestVersion"
77-
+ $"&$orderby=Id&searchTerm='{HttpUtility.UrlEncode(query)}'"
78-
+ $"&targetFramework=''"
79-
+ $"&includePrerelease={(canPrerelease ? "true" : "false")}"
80-
+ $"&$skip=0"
81-
+ $"&$top=50"
82-
+ $"&semVerLevel=2.0.0"
83-
);
84-
// Uri SearchUrl = new($"{source.Url}/Search()?$filter=IsLatestVersion&searchTerm=%27{HttpUtility.UrlEncode(query)}%27&targetFramework=%27%27&includePrerelease=false");
81+
string versionFilter = canPrerelease ? "IsAbsoluteLatestVersion eq true" : "IsLatestVersion eq true";
82+
string odataQuery = HttpUtility.UrlEncode(query.Replace("'", "''"));
83+
Uri? SearchUrl = UseSubstringSearch
84+
? new Uri(
85+
$"{source.Url}/Packages()"
86+
+ $"?$filter=substringof('{odataQuery}',Id) and {versionFilter}"
87+
+ $"&$orderby=DownloadCount desc"
88+
+ $"&$skip=0"
89+
+ $"&$top=50"
90+
)
91+
: new Uri(
92+
$"{source.Url}/Search()"
93+
+ $"?$filter=IsLatestVersion"
94+
+ $"&$orderby=Id&searchTerm='{odataQuery}'"
95+
+ $"&targetFramework=''"
96+
+ $"&includePrerelease={(canPrerelease ? "true" : "false")}"
97+
+ $"&$skip=0"
98+
+ $"&$top=50"
99+
+ $"&semVerLevel=2.0.0"
100+
);
85101
logger.Log($"Begin package search with url={SearchUrl} on manager {Name}");
86102
Dictionary<string, SearchResult> AlreadyProcessedPackages = [];
87103

src/UniGetUI.PackageEngine.Managers.Pip/Helpers/PipPkgOperationHelper.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ package.OverridenOptions.Scope is null
5252
parameters.Add("--user");
5353
}
5454

55+
if (package.OverridenOptions.Pip_BreakSystemPackages)
56+
parameters.Add("--break-system-packages");
57+
5558
parameters.Add(Pip.GetProxyArgument());
5659

5760
parameters.AddRange(
@@ -80,6 +83,12 @@ int returnCode
8083

8184
string output_string = string.Join("\n", processOutput);
8285

86+
if (output_string.Contains("externally-managed-environment") && !package.OverridenOptions.Pip_BreakSystemPackages)
87+
{
88+
package.OverridenOptions.Pip_BreakSystemPackages = true;
89+
return OperationVeredict.AutoRetry;
90+
}
91+
8392
if (output_string.Contains("--user") && package.OverridenOptions.Scope != PackageScope.User)
8493
{
8594
package.OverridenOptions.Scope = PackageScope.User;

0 commit comments

Comments
 (0)