Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,7 @@ build/
offline-repository/
buildSrc/libs
.vscode/

# Resource files (.tar.gz, .zip) that are generated during the build process
*.tar.gz
*.zip
401 changes: 401 additions & 0 deletions WPILibInstaller-Avalonia/CliInstaller.cs

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions WPILibInstaller-Avalonia/Interfaces/IArchiveExtractionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using WPILibInstaller.Models;

namespace WPILibInstaller.Interfaces
{
public interface IArchiveExtractionService
{
Task ExtractArchive(CancellationToken token, string[]? filter, IProgress<InstallProgress>? progress = null);
Task ExtractJDKAndTools(CancellationToken token, IProgress<InstallProgress>? progress = null);
}
}
10 changes: 10 additions & 0 deletions WPILibInstaller-Avalonia/Interfaces/IShortcutService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading;
using System.Threading.Tasks;

namespace WPILibInstaller.Interfaces
{
public interface IShortcutService
{
Task RunShortcutCreator(CancellationToken token);
}
}
14 changes: 14 additions & 0 deletions WPILibInstaller-Avalonia/Interfaces/IToolInstallationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
using WPILibInstaller.Models;

namespace WPILibInstaller.Interfaces
{
public interface IToolInstallationService
{
Task RunGradleSetup(IProgress<InstallProgress>? progress = null);
Task RunToolSetup(IProgress<InstallProgress>? progress = null);
Task RunCppSetup(IProgress<InstallProgress>? progress = null);
Task RunMavenMetaDataFixer(IProgress<InstallProgress>? progress = null);
}
}
14 changes: 14 additions & 0 deletions WPILibInstaller-Avalonia/Interfaces/IVsCodeInstallationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using WPILibInstaller.Models;

namespace WPILibInstaller.Interfaces
{
public interface IVsCodeInstallationService
{
Task RunVsCodeSetup(CancellationToken token, IProgress<InstallProgress>? progress = null);
Task ConfigureVsCodeSettings();
Task RunVsCodeExtensionsSetup(IProgress<InstallProgress>? progress = null);
}
}
9 changes: 9 additions & 0 deletions WPILibInstaller-Avalonia/Models/InstallProgress.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace WPILibInstaller.Models
{
/// <summary>
/// Represents the progress of an installation process, including the percentage completed and a status message.
/// </summary>
/// <param name="Percentage">The percentage of the installation that has been completed.</param>
/// <param name="StatusText">A message describing the current status of the installation.</param>
public record InstallProgress(int Percentage, string StatusText);
}
71 changes: 68 additions & 3 deletions WPILibInstaller-Avalonia/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Avalonia;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.ReactiveUI;

namespace WPILibInstaller
Expand All @@ -8,8 +10,71 @@ class Program
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
public static int Main(string[] args)
{
// Check for help
if (args.Contains("--help") || args.Contains("-h"))
{
PrintHelp();
return 0;
}

// Check for CLI mode
if (args.Contains("--install") || args.Contains("-i"))
{
return RunCliMode(args).GetAwaiter().GetResult();
}

// Run GUI mode
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
return 0;
}

private static void PrintHelp()
{
System.Console.WriteLine("WPILib Installer");
System.Console.WriteLine();
System.Console.WriteLine("Usage:");
System.Console.WriteLine(" WPILibInstaller [options]");
System.Console.WriteLine();
System.Console.WriteLine("Options:");
System.Console.WriteLine(" -i, --install Run in CLI installation mode");
System.Console.WriteLine(" -a, --all-users Install for all users (requires admin/sudo)");
System.Console.WriteLine(" --install-mode <mode> Installation mode: 'all' or 'tools' (default: all)");
System.Console.WriteLine(" - all: Full installation with VS Code");
System.Console.WriteLine(" - tools: Tools only (JDK + WPILib tools)");
System.Console.WriteLine(" -h, --help Show this help message");
System.Console.WriteLine();
System.Console.WriteLine("Offline Installation:");
System.Console.WriteLine(" If VS Code download fails, the installer will automatically check for an");
System.Console.WriteLine(" offline archive named 'vscode.zip' or 'vscode.tar.gz' in the same directory.");
System.Console.WriteLine(" You can pre-download VS Code and save it with this name for offline installs.");
System.Console.WriteLine();
System.Console.WriteLine("Examples:");
System.Console.WriteLine(" WPILibInstaller --install");
System.Console.WriteLine(" WPILibInstaller --install --all-users");
System.Console.WriteLine(" WPILibInstaller --install --install-mode tools");
System.Console.WriteLine();
}

private static async Task<int> RunCliMode(string[] args)
{
bool allUsers = args.Contains("--all-users") || args.Contains("-a");

// Parse install mode (default to "all")
string installMode = "all";
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "--install-mode" && i + 1 < args.Length)
{
installMode = args[i + 1].ToLowerInvariant();
break;
}
}

var installer = new CliInstaller();
return await installer.RunInstallAsync(allUsers, installMode);
}

// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
Expand Down
167 changes: 167 additions & 0 deletions WPILibInstaller-Avalonia/Services/ArchiveExtractionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MsBox.Avalonia;
using MsBox.Avalonia.Dto;
using WPILibInstaller.Interfaces;
using WPILibInstaller.Models;


namespace WPILibInstaller.Services
{
public class ArchiveExtractionService : IArchiveExtractionService
{
private readonly IConfigurationProvider configurationProvider;
private readonly IProgramWindow programWindow;

public ArchiveExtractionService(IConfigurationProvider configurationProvider, IProgramWindow programWindow)
{
this.configurationProvider = configurationProvider;
this.programWindow = programWindow;
}

public async Task ExtractJDKAndTools(CancellationToken token, IProgress<InstallProgress>? progress = null)
{
await ExtractArchive(token, new[] {
configurationProvider.JdkConfig.Folder + "/",
configurationProvider.UpgradeConfig.Tools.Folder + "/",
configurationProvider.AdvantageScopeConfig.Folder + "/",
configurationProvider.ElasticConfig.Folder + "/",
"installUtils/", "icons"}, progress);
}

public async Task ExtractArchive(CancellationToken token, string[]? filter, IProgress<InstallProgress>? progress = null)
{
progress?.Report(new InstallProgress(0, "Starting extraction"));

if (OperatingSystem.IsWindows())
{
progress?.Report(new InstallProgress(0, "Checking for currently running JDKs"));
bool foundRunningExe = await Task.Run(() =>
{
try
{
var jdkBinFolder = Path.Join(configurationProvider.InstallDirectory, configurationProvider.JdkConfig.Folder, "bin");
var jdkExes = Directory.EnumerateFiles(jdkBinFolder, "*.exe", SearchOption.AllDirectories);
bool found = false;
foreach (var exe in jdkExes)
{
try
{
var name = Path.GetFileNameWithoutExtension(exe)!;
var pNames = Process.GetProcessesByName(name);
foreach (var p in pNames)
{
if (p.MainModule?.FileName == exe)
{
found = true;
break;
}
}
if (found)
{
break;
}
}
catch
{
// Do nothing. We don't want this code to break.
}
}
return found;
}
catch
{
// Do nothing. We don't want this code to break.
return false;
}
});
if (foundRunningExe)
{
string msg = "Running JDK processes have been found. Installation cannot continue. Please restart your computer, and rerun this installer without running anything else (including VS Code)";
await MessageBoxManager.GetMessageBoxStandard(new MessageBoxStandardParams
{
ContentTitle = "JDKs Running",
ContentMessage = msg,
Icon = MsBox.Avalonia.Enums.Icon.Error,
ButtonDefinitions = MsBox.Avalonia.Enums.ButtonEnum.Ok
}).ShowWindowDialogAsync(programWindow.Window);
throw new InvalidOperationException(msg);
}
}

var archive = configurationProvider.ZipArchive;

var extractor = archive;

double totalSize = extractor.TotalUncompressSize;
long currentSize = 0;

string intoPath = configurationProvider.InstallDirectory;

while (extractor.MoveToNextEntry())
{
if (token.IsCancellationRequested)
{
return;
}
currentSize += extractor.EntrySize;
if (extractor.EntryIsDirectory) continue;

var entryName = extractor.EntryKey;
if (filter != null)
{
bool skip = true;
foreach (var keep in filter)
{
if (entryName.StartsWith(keep))
{
skip = false;
break;
}
}

if (skip)
{
continue;
}
}


double currentPercentage = (currentSize / totalSize) * 100;
if (currentPercentage > 100) currentPercentage = 100;
if (currentPercentage < 0) currentPercentage = 0;

progress?.Report(new InstallProgress((int)currentPercentage, "Installing " + entryName));

string fullZipToPath = Path.Combine(intoPath, entryName);
string? directoryName = Path.GetDirectoryName(fullZipToPath);
if (directoryName?.Length > 0)
{
try
{
Directory.CreateDirectory(directoryName);
}
catch (IOException)
{

}
}

{
using FileStream writer = File.Create(fullZipToPath);
await extractor.CopyToStreamAsync(writer);
}

if (extractor.EntryIsExecutable && !OperatingSystem.IsWindows())
{
var currentMode = File.GetUnixFileMode(fullZipToPath);
File.SetUnixFileMode(fullZipToPath, currentMode | UnixFileMode.GroupExecute | UnixFileMode.UserExecute | UnixFileMode.OtherExecute);
}
}
}
}
}
Loading
Loading