Skip to content

Commit abb46cd

Browse files
Add commands to install missing and uninstall unused editors
1 parent 6ff85e4 commit abb46cd

10 files changed

Lines changed: 288 additions & 126 deletions
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
class InstallationsInstallMissingCommand : BaseCommand<MutatingCommand>
2+
{
3+
protected override int ExecuteImpl(MutatingCommand settings)
4+
{
5+
var versions = VersionUsage.PerformSearch();
6+
var missingVersions = versions.UsedNotInstalled.ToList();
7+
8+
if (missingVersions.Count == 0)
9+
{
10+
WriteSuccess("All used Unity versions are already installed.");
11+
return 0;
12+
}
13+
14+
AnsiConsole.MarkupLine($"[bold]Found {missingVersions.Count} missing Unity version(s):[/]");
15+
AnsiConsole.WriteLine();
16+
17+
foreach (string version in missingVersions)
18+
{
19+
AnsiConsole.MarkupLine($" [yellow]•[/] {Markup.Escape(version)}");
20+
}
21+
22+
AnsiConsole.WriteLine();
23+
24+
bool shouldInstall = AnsiConsole.Confirm(
25+
"Do you want to install these versions?",
26+
defaultValue: false);
27+
28+
if (!shouldInstall)
29+
{
30+
AnsiConsole.MarkupLine("[yellow]Installation cancelled.[/]");
31+
return 0;
32+
}
33+
34+
AnsiConsole.WriteLine();
35+
var unityHub = new UnityHub(settings.MutatingProcess);
36+
37+
foreach (string version in missingVersions)
38+
{
39+
AnsiConsole.MarkupLine($"[bold]Installing Unity {Markup.Escape(version)}...[/]");
40+
41+
try
42+
{
43+
string[] additionalArgs = context.Remaining.Raw.ToArray();
44+
unityHub.EnsureEditorInstalled(version, changeset: null, additionalArgs);
45+
WriteSuccess($"Unity {version} installed successfully.");
46+
}
47+
catch (Exception ex)
48+
{
49+
WriteError($"Failed to install Unity {version}: {ex.Message}");
50+
}
51+
52+
AnsiConsole.WriteLine();
53+
}
54+
55+
WriteSuccess("Installation process completed.");
56+
return 0;
57+
}
58+
}

dotnet/ucll/Installations/InstallationsOverviewCommand.cs

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,28 @@ class InstallationsOverviewCommand : BaseCommand<InstallationsOverviewSettings>
22
{
33
protected override int ExecuteImpl(InstallationsOverviewSettings settings)
44
{
5-
var editorVersions = UnityHub.ListInstalledEditors().Select(i => i.Version).ToHashSet();
6-
var usedEditorVersions = FindUnityProjects().Select(p => ProjectVersionFile.Parse(p, out string _).Version)
7-
.ToHashSet();
5+
var versions = VersionUsage.PerformSearch();
86

9-
if (settings.Parseable)
10-
PrintParseable(editorVersions, usedEditorVersions);
7+
if (settings.PlainText)
8+
PrintParseable(versions);
119
else
12-
PrintTable(editorVersions, usedEditorVersions);
10+
PrintTable(versions);
1311

1412
return 0;
1513
}
1614

17-
private static void PrintTable(HashSet<string> editorVersions, HashSet<string> usedEditorVersions)
15+
private static void PrintTable(VersionUsage versions)
1816
{
1917
var table = new Table();
2018
table.Border(TableBorder.Rounded);
2119
table.AddColumn("[bold]Version[/]");
2220
table.AddColumn("[bold]Installed[/]");
2321
table.AddColumn("[bold]Used[/]");
2422

25-
foreach (string version in editorVersions.Union(usedEditorVersions).Order())
23+
foreach (string version in versions.Installed.Union(versions.Used))
2624
{
27-
bool isInstalled = editorVersions.Contains(version);
28-
bool isUsed = usedEditorVersions.Contains(version);
25+
bool isInstalled = versions.Installed.Contains(version);
26+
bool isUsed = versions.Used.Contains(version);
2927

3028
static string GetIcon(bool value) => value ? "[green]✓[/]" : "[red]✗[/]";
3129

@@ -35,47 +33,37 @@ private static void PrintTable(HashSet<string> editorVersions, HashSet<string> u
3533
AnsiConsole.Write(table);
3634

3735
AnsiConsole.WriteLine();
38-
AnsiConsole.MarkupLine($"[bold]Summary:[/]");
39-
AnsiConsole.MarkupLine($" Installed versions: [cyan]{editorVersions.Count}[/]");
40-
AnsiConsole.MarkupLine($" Used versions: [cyan]{usedEditorVersions.Count}[/]");
41-
AnsiConsole.MarkupLine($" Used but not installed: [yellow]{usedEditorVersions.Except(editorVersions).Count()}[/]");
42-
AnsiConsole.MarkupLine($" Installed but not used: [yellow]{editorVersions.Except(usedEditorVersions).Count()}[/]");
36+
AnsiConsole.MarkupLine("[bold]Summary:[/]");
37+
AnsiConsole.MarkupLine($" Installed versions: [cyan]{versions.Installed.Count}[/]");
38+
AnsiConsole.MarkupLine($" Used versions: [cyan]{versions.Used.Count}[/]");
39+
AnsiConsole.MarkupLine($" Used but not installed: [yellow]{versions.UsedNotInstalled.Count}[/]");
40+
AnsiConsole.MarkupLine($" Installed but not used: [yellow]{versions.InstalledNotUsed.Count}[/]");
4341
}
4442

45-
private static void PrintParseable(HashSet<string> editorVersions, HashSet<string> usedEditorVersions)
43+
private static void PrintParseable(VersionUsage installs)
4644
{
47-
Console.WriteLine("Installed Unity versions: " + editorVersions.Count);
48-
foreach (string version in editorVersions.Order())
45+
Console.WriteLine("# Installed versions: " + installs.Installed.Count);
46+
foreach (string version in installs.Installed)
4947
{
5048
Console.WriteLine(version);
5149
}
5250

53-
Console.WriteLine("Used Unity versions: " + usedEditorVersions.Count);
54-
foreach (string version in usedEditorVersions.Order())
51+
Console.WriteLine("# Used versions: " + installs.Used.Count);
52+
foreach (string version in installs.Used)
5553
{
5654
Console.WriteLine(version);
5755
}
5856

59-
Console.WriteLine("Used versions that are not installed: ");
60-
foreach (string version in usedEditorVersions.Except(editorVersions).Order())
57+
Console.WriteLine("# Used versions that are not installed: " + installs.UsedNotInstalled.Count);
58+
foreach (string version in installs.UsedNotInstalled)
6159
{
6260
Console.WriteLine(version);
6361
}
6462

65-
Console.WriteLine("Installed versions that are not used: ");
66-
foreach (string version in editorVersions.Except(usedEditorVersions).Order())
63+
Console.WriteLine("# Installed versions that are not used: " + installs.InstalledNotUsed.Count);
64+
foreach (string version in installs.InstalledNotUsed)
6765
{
6866
Console.WriteLine(version);
6967
}
7068
}
71-
72-
private static IEnumerable<string> FindUnityProjects()
73-
{
74-
var startInfo = PlatformHelper.GetUnityProjectSearchProcess();
75-
startInfo.RedirectStandardOutput = true;
76-
var process = ProcessRunner.Default.Run(startInfo);
77-
process.WaitForExit();
78-
while (!process.StandardOutput.EndOfStream)
79-
yield return process.StandardOutput.ReadLine()!;
80-
}
8169
}

dotnet/ucll/Installations/InstallationsOverviewSettings.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
class InstallationsOverviewSettings : CommandSettings
44
{
5-
[CommandOption("--parseable")]
5+
[CommandOption("-p|--plaintext|--plain")]
66
[Description("Output in a simple machine-parseable format")]
7-
public bool Parseable { get; init; }
7+
public bool PlainText { get; init; }
88
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
class InstallationsUninstallUnusedCommand : BaseCommand<MutatingCommand>
2+
{
3+
protected override int ExecuteImpl(MutatingCommand settings)
4+
{
5+
var versions = VersionUsage.PerformSearch();
6+
var unusedVersions = versions.InstalledNotUsed.ToList();
7+
8+
if (unusedVersions.Count == 0)
9+
{
10+
WriteSuccess("All installed Unity versions are being used.");
11+
return 0;
12+
}
13+
14+
AnsiConsole.MarkupLine($"[bold]Found {unusedVersions.Count} unused Unity version(s):[/]");
15+
AnsiConsole.WriteLine();
16+
17+
var versionPaths = new Dictionary<string, string>();
18+
foreach (string version in unusedVersions.OrderBy(v => v))
19+
{
20+
try
21+
{
22+
string editorPath = UnityHub.GetEditorPath(version);
23+
string installDir = PlatformSupport.GetInstallationRootDirectory(editorPath);
24+
versionPaths[version] = installDir;
25+
26+
AnsiConsole.MarkupLine($" [yellow]•[/] {Markup.Escape(version)}");
27+
AnsiConsole.MarkupLine($" [dim]{Markup.Escape(installDir)}[/]");
28+
}
29+
catch (Exception ex)
30+
{
31+
AnsiConsole.MarkupLine(
32+
$" [red]•[/] {Markup.Escape(version)} [dim](Error: {Markup.Escape(ex.Message)})[/]");
33+
}
34+
}
35+
36+
if (versionPaths.Count == 0)
37+
{
38+
WriteError("No valid installation directories found.");
39+
return 1;
40+
}
41+
42+
AnsiConsole.WriteLine();
43+
44+
bool shouldUninstall = AnsiConsole.Confirm(
45+
"Do you want to uninstall these versions?",
46+
defaultValue: false);
47+
48+
if (!shouldUninstall)
49+
{
50+
AnsiConsole.MarkupLine("[yellow]Uninstallation cancelled.[/]");
51+
return 0;
52+
}
53+
54+
AnsiConsole.WriteLine();
55+
56+
foreach (var (version, installDir) in versionPaths)
57+
{
58+
AnsiConsole.MarkupLine($"[bold]Uninstalling Unity {Markup.Escape(version)}...[/]");
59+
60+
try
61+
{
62+
if (settings.DryRun)
63+
{
64+
AnsiConsole.MarkupLine($"[dim]Would delete: {Markup.Escape(installDir)}[/]");
65+
}
66+
else
67+
{
68+
Directory.Delete(installDir, recursive: true);
69+
}
70+
71+
WriteSuccess($"Unity {version} uninstalled successfully.");
72+
}
73+
catch (Exception ex)
74+
{
75+
WriteError($"Failed to uninstall Unity {version}: {ex.Message}");
76+
}
77+
78+
AnsiConsole.WriteLine();
79+
}
80+
81+
WriteSuccess("Uninstallation process completed.");
82+
return 0;
83+
}
84+
}

dotnet/ucll/Installations/InstallationsUsedCommand.cs

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,11 @@ class InstallationsUsedCommand : BaseCommand<InstallationsUsedSettings>
22
{
33
protected override int ExecuteImpl(InstallationsUsedSettings settings)
44
{
5-
var projectsUsingVersion = FindProjectsUsingVersion(settings.Version).ToList();
6-
foreach (string project in projectsUsingVersion)
5+
foreach (string project in VersionUsage.FindUnityProjects()
6+
.Where(path => ProjectVersionFile.Parse(path).Version == settings.Version))
77
{
88
Console.WriteLine(project);
99
}
1010
return 0;
1111
}
12-
13-
private static IEnumerable<string> FindProjectsUsingVersion(string version)
14-
{
15-
var startInfo = PlatformHelper.GetUnityProjectSearchProcess();
16-
startInfo.RedirectStandardOutput = true;
17-
var process = ProcessRunner.Default.Run(startInfo);
18-
process.WaitForExit();
19-
20-
while (!process.StandardOutput.EndOfStream)
21-
{
22-
string projectPath = process.StandardOutput.ReadLine()!;
23-
var projectVersion = ProjectVersionFile.Parse(projectPath, out string _).Version;
24-
25-
if (projectVersion == version)
26-
{
27-
yield return projectPath;
28-
}
29-
}
30-
}
3112
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
class VersionUsage
2+
{
3+
public HashSet<string> Installed { get; }
4+
public HashSet<string> Used { get; }
5+
public HashSet<string> UsedNotInstalled => Used.Except(Installed).ToHashSet();
6+
public HashSet<string> InstalledNotUsed => Installed.Except(Used).ToHashSet();
7+
8+
public static VersionUsage PerformSearch() => new VersionUsage();
9+
10+
private VersionUsage()
11+
{
12+
Installed = UnityHub.ListInstalledEditors()
13+
.Select(i => i.Version)
14+
.ToHashSet();
15+
16+
Used = FindUnityProjects()
17+
.Select(p => ProjectVersionFile.Parse(p, out string _).Version)
18+
.ToHashSet();
19+
}
20+
21+
public static IEnumerable<string> FindUnityProjects()
22+
{
23+
var startInfo = PlatformSupport.GetUnityProjectSearchProcess();
24+
startInfo.RedirectStandardOutput = true;
25+
var process = ProcessRunner.Default.Run(startInfo);
26+
process.WaitForExit();
27+
28+
while (!process.StandardOutput.EndOfStream)
29+
yield return process.StandardOutput.ReadLine()!;
30+
}
31+
}

dotnet/ucll/Open/UnityHub.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ partial class UnityHub(IProcessRunner mutatingProcessRunner)
1515
public static string GetEditorPath(string version)
1616
{
1717
// Fast path: check default install location first
18-
var fastPath = PlatformHelper.FindDefaultEditorInstallPath(version);
18+
var fastPath = PlatformSupport.FindDefaultEditorInstallPath(version);
1919
if (fastPath != null)
2020
return fastPath;
2121

@@ -26,12 +26,12 @@ public static string GetEditorPath(string version)
2626
if (path == null)
2727
throw new Exception($"Unity version {version} is not installed.");
2828

29-
return path;
29+
return Path.Combine(path, PlatformSupport.GetRelativeEditorPathToExecutable());
3030
}
3131

3232
public static IEnumerable<string> GetRecentProjects(bool favoritesOnly = false)
3333
{
34-
var configDir = PlatformHelper.GetUnityHubConfigDirectory();
34+
var configDir = PlatformSupport.GetUnityHubConfigDirectory();
3535
var projectsFile = Path.Combine(configDir, "projects-v1.json");
3636

3737
try
@@ -89,7 +89,7 @@ public static List<EditorInfo> ListInstalledEditors()
8989
var hubPath = GetUnityHubPath();
9090

9191
var process = ProcessRunner.Default.Run(
92-
new ProcessStartInfo(hubPath, PlatformHelper.FormatHubArgs("--headless editors --installed"))
92+
new ProcessStartInfo(hubPath, PlatformSupport.FormatHubArgs("--headless editors --installed"))
9393
{ RedirectStandardOutput = true, RedirectStandardError = true });
9494
var output = process.StandardOutput.ReadToEnd();
9595
process.WaitForExit();
@@ -132,7 +132,7 @@ private static string GetUnityHubPath()
132132
if (_hubPathCache != null)
133133
return _hubPathCache;
134134

135-
_hubPathCache = PlatformHelper.FindDefaultHubInstallPath();
135+
_hubPathCache = PlatformSupport.FindDefaultHubInstallPath();
136136

137137
if (_hubPathCache == null)
138138
throw new Exception("Unity Hub not found.");
@@ -143,7 +143,7 @@ private static string GetUnityHubPath()
143143
private static bool IsEditorInstalled(string version)
144144
{
145145
// Fast path: check default install location first
146-
if (PlatformHelper.FindDefaultEditorInstallPath(version) != null)
146+
if (PlatformSupport.FindDefaultEditorInstallPath(version) != null)
147147
return true;
148148

149149
// Fallback: query Unity Hub for custom installation locations
@@ -179,7 +179,7 @@ private void InstallEditor(string version, string changeset, string[] additional
179179

180180
var process = mutatingProcessRunner.Run(new ProcessStartInfo(
181181
hubPath,
182-
PlatformHelper.FormatHubArgs(args)) { RedirectStandardError = true });
182+
PlatformSupport.FormatHubArgs(args)) { RedirectStandardError = true });
183183
process.WaitForExit();
184184

185185
if (process.ExitCode != 0)

0 commit comments

Comments
 (0)