diff --git a/.gitignore b/.gitignore index 5bb8b02..bcf4b29 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ GodotEnv.sln.DotSettings.user # File-based project format *.iws + +### Visual Studio ### +.vs/ diff --git a/GodotEnv.Tests/src/common/clients/FileClientTest.cs b/GodotEnv.Tests/src/common/clients/FileClientTest.cs index 4b017c3..98891bd 100644 --- a/GodotEnv.Tests/src/common/clients/FileClientTest.cs +++ b/GodotEnv.Tests/src/common/clients/FileClientTest.cs @@ -42,6 +42,7 @@ public TestJsonModel(string name) { [Fact] public void InitializesLinux() { FileClient.IsOSPlatform = (platform) => platform == OSPlatform.Linux; + FileClient.ProcessorArchitecture = Architecture.X64; var fs = GetFs('/'); var computer = new Mock(); var client = new FileClient(fs.Object, computer.Object, new Mock().Object); @@ -50,12 +51,15 @@ public void InitializesLinux() { client.OSFamily.ShouldBe(OSFamily.Unix); client.Separator.ShouldBe('/'); client.OS.ShouldBe(OSType.Linux); + client.Processor.ShouldBe(ProcessorType.other); FileClient.IsOSPlatform = FileClient.IsOSPlatformDefault; + FileClient.ProcessorArchitecture = FileClient.ProcessorArchitectureDefault; } [Fact] public void InitializesMacOS() { FileClient.IsOSPlatform = (platform) => platform == OSPlatform.OSX; + FileClient.ProcessorArchitecture = Architecture.X64; var fs = GetFs('/'); var computer = new Mock(); var client = new FileClient( @@ -66,7 +70,9 @@ public void InitializesMacOS() { client.OSFamily.ShouldBe(OSFamily.Unix); client.Separator.ShouldBe('/'); client.OS.ShouldBe(OSType.MacOS); + client.Processor.ShouldBe(ProcessorType.other); FileClient.IsOSPlatform = FileClient.IsOSPlatformDefault; + FileClient.ProcessorArchitecture = FileClient.ProcessorArchitectureDefault; } [Fact] @@ -82,19 +88,42 @@ public void InitializesWindows() { client.OSFamily.ShouldBe(OSFamily.Windows); client.Separator.ShouldBe('\\'); client.OS.ShouldBe(OSType.Windows); + client.Processor.ShouldBe(ProcessorType.arm64); FileClient.IsOSPlatform = FileClient.IsOSPlatformDefault; } + [Fact] + public void InitializesWindowsArm() { + FileClient.IsOSPlatform = (platform) => platform == OSPlatform.Windows; + FileClient.ProcessorArchitecture = Architecture.Arm64; + var fs = GetFs('\\'); + var computer = new Mock(); + var client = new FileClient( + fs.Object, computer.Object, new Mock().Object + ); + client.ShouldBeAssignableTo(); + client.Files.ShouldBe(fs.Object); + client.OSFamily.ShouldBe(OSFamily.Windows); + client.Separator.ShouldBe('\\'); + client.OS.ShouldBe(OSType.Windows); + client.Processor.ShouldBe(ProcessorType.arm64); + FileClient.IsOSPlatform = FileClient.IsOSPlatformDefault; + FileClient.ProcessorArchitecture = FileClient.ProcessorArchitectureDefault; + } + [Fact] public void InitializesUnknownOS() { FileClient.IsOSPlatform = (platform) => false; + FileClient.ProcessorArchitecture = Architecture.X64; var fs = GetFs('\\'); var computer = new Mock(); var client = new FileClient( fs.Object, computer.Object, new Mock().Object ); client.OS.ShouldBe(OSType.Unknown); + client.Processor.ShouldBe(ProcessorType.other); FileClient.IsOSPlatform = FileClient.IsOSPlatformDefault; + FileClient.ProcessorArchitecture = FileClient.ProcessorArchitectureDefault; } [Fact] diff --git a/GodotEnv.Tests/src/features/godot/models/GodotEnvironmentTest.cs b/GodotEnv.Tests/src/features/godot/models/GodotEnvironmentTest.cs index 0193800..d6c898b 100644 --- a/GodotEnv.Tests/src/features/godot/models/GodotEnvironmentTest.cs +++ b/GodotEnv.Tests/src/features/godot/models/GodotEnvironmentTest.cs @@ -1,5 +1,6 @@ namespace Chickensoft.GodotEnv.Tests.features.godot.models; +using Chickensoft.GodotEnv.Common.Models; using Chickensoft.GodotEnv.Features.Godot.Models; using Common.Clients; using Common.Utilities; @@ -37,6 +38,8 @@ public void GetsExpectedMacMonoDownloadUrl() { [Fact] public void GetsExpectedWindowsDownloadUrl() { + fileClient.Setup(f => f.Processor).Returns(ProcessorType.other); + var platform = new Windows(fileClient.Object, computer.Object); var downloadUrl = platform.GetDownloadUrl(version4, false, false); @@ -48,6 +51,8 @@ public void GetsExpectedWindowsDownloadUrl() { [Fact] public void GetsExpectedWindowsMonoDownloadUrl() { + fileClient.Setup(f => f.Processor).Returns(ProcessorType.other); + var platform = new Windows(fileClient.Object, computer.Object); var downloadUrl = platform.GetDownloadUrl(version4, true, false); @@ -57,6 +62,32 @@ public void GetsExpectedWindowsMonoDownloadUrl() { downloadUrl.ShouldBe(GetExpectedDownloadUrl(version3, "stable_mono_win64")); } + [Fact] + public void GetsExpectedWindowsArmDownloadUrl() { + fileClient.Setup(f => f.Processor).Returns(ProcessorType.arm64); + + var platform = new Windows(fileClient.Object, computer.Object); + + var downloadUrl = platform.GetDownloadUrl(version4, false, false); + downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}4.1.2-stable/Godot_v4.1.2-stable_windows_arm64.exe.zip"); + + downloadUrl = platform.GetDownloadUrl(version3, false, false); + downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}{version3.VersionString}-stable/Godot_v{version3.VersionString}-stable_windows_arm64.exe.zip"); + } + + [Fact] + public void GetsExpectedWindowsArmMonoDownloadUrl() { + fileClient.Setup(f => f.Processor).Returns(ProcessorType.arm64); + + var platform = new Windows(fileClient.Object, computer.Object); + + var downloadUrl = platform.GetDownloadUrl(version4, true, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version4, "stable_mono_windows_arm64")); + + downloadUrl = platform.GetDownloadUrl(version3, true, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version3, "stable_mono_windows_arm64")); + } + [Fact] public void GetsExpectedLinuxDownloadUrl() { var platform = new Linux(fileClient.Object, computer.Object); diff --git a/GodotEnv/src/common/clients/FileClient.cs b/GodotEnv/src/common/clients/FileClient.cs index 50d7e38..cad65df 100644 --- a/GodotEnv/src/common/clients/FileClient.cs +++ b/GodotEnv/src/common/clients/FileClient.cs @@ -31,6 +31,9 @@ public interface IFileClient { /// The operating system type. OSType OS { get; } + /// The process architecture type. + ProcessorType Processor { get; } + /// Path directory separator. char Separator { get; } @@ -309,6 +312,7 @@ public class FileClient : IFileClient { public IProcessRunner ProcessRunner { get; } public OSFamily OSFamily { get; } public OSType OS { get; } + public ProcessorType Processor { get; } public char Separator { get; } // Shims for testing. @@ -319,6 +323,12 @@ public class FileClient : IFileClient { public static Func IsOSPlatform { get; set; } = IsOSPlatformDefault; + public static Architecture ProcessorArchitectureDefault { get; } = + RuntimeInformation.ProcessArchitecture; + + public static Architecture ProcessorArchitecture { get; set; } = + ProcessorArchitectureDefault; + public string UserDirectory => Path.TrimEndingDirectorySeparator( Environment.GetFolderPath( Environment.SpecialFolder.UserProfile, @@ -353,6 +363,9 @@ public FileClient( : IsOSPlatform(OSPlatform.Windows) ? OSType.Windows : OSType.Unknown; + Processor = ProcessorArchitecture == Architecture.Arm64 + ? ProcessorType.arm64 + : ProcessorType.other; } public string Sanitize(string path) => diff --git a/GodotEnv/src/common/models/ProcessorType.cs b/GodotEnv/src/common/models/ProcessorType.cs new file mode 100644 index 0000000..f58e84e --- /dev/null +++ b/GodotEnv/src/common/models/ProcessorType.cs @@ -0,0 +1,11 @@ +namespace Chickensoft.GodotEnv.Common.Models; + +/// +/// Processor architecture type. +/// +public enum ProcessorType { + /// arm64. + arm64, + /// Other. + other, +} diff --git a/GodotEnv/src/features/godot/models/Windows.cs b/GodotEnv/src/features/godot/models/Windows.cs index 4d67192..81fd3be 100644 --- a/GodotEnv/src/features/godot/models/Windows.cs +++ b/GodotEnv/src/features/godot/models/Windows.cs @@ -3,19 +3,25 @@ namespace Chickensoft.GodotEnv.Features.Godot.Models; using System.IO.Abstractions; using System.Threading.Tasks; using Chickensoft.GodotEnv.Common.Clients; +using Chickensoft.GodotEnv.Common.Models; using Chickensoft.GodotEnv.Common.Utilities; public class Windows : GodotEnvironment { public Windows(IFileClient fileClient, IComputer computer) : base(fileClient, computer) { } + private string ProcessorArchitecture => + FileClient.Processor == ProcessorType.arm64 + ? "windows_arm64" + : "win64"; + public override string ExportTemplatesBasePath => FileClient.GetFullPath( FileClient.Combine(FileClient.UserDirectory, "\\AppData\\Roaming\\Godot") ); public override string GetInstallerNameSuffix(bool isDotnetVersion, SemanticVersion version) => - isDotnetVersion ? "_mono_win64" : "_win64.exe"; + isDotnetVersion ? $"_mono_{ProcessorArchitecture}" : $"_{ProcessorArchitecture}.exe"; public override Task IsExecutable(IShell shell, IFileInfo file) => Task.FromResult(file.Name.ToLower().EndsWith(".exe")); @@ -27,13 +33,13 @@ public override string GetRelativeExtractedExecutablePath( ) { var fsVersionString = GetFilenameVersionString(version); var name = fsVersionString + - $"{(isDotnetVersion ? "_mono" : "")}_win64.exe"; + $"{(isDotnetVersion ? "_mono" : "")}_{ProcessorArchitecture}.exe"; // Both versions extract to a folder. The dotnet folder name is different // from the non-dotnet folder name :P if (isDotnetVersion) { - return FileClient.Combine(fsVersionString + "_mono_win64", name); + return FileClient.Combine(fsVersionString + $"_mono_{ProcessorArchitecture}", name); } // There is no subfolder for non-dotnet versions. @@ -44,6 +50,6 @@ public override string GetRelativeGodotSharpPath( SemanticVersion version, bool isDotnetVersion ) => FileClient.Combine( - GetFilenameVersionString(version) + "_mono_win64", "GodotSharp" + GetFilenameVersionString(version) + $"_mono_{ProcessorArchitecture}", "GodotSharp" ); }