diff --git a/.github/workflows/install_test.yaml b/.github/workflows/install_test.yaml index f17e04a..d981f5a 100644 --- a/.github/workflows/install_test.yaml +++ b/.github/workflows/install_test.yaml @@ -4,8 +4,90 @@ on: pull_request: jobs: - install_tests: - name: ๐Ÿ”‹ Integration Tests with ${{ matrix.os }} + install_tests_3x: + name: ๐Ÿ”‹ Godot 3.x Integration Tests with ${{ matrix.os }} + runs-on: ${{ matrix.os }} + # Only run the workflow if it's not a PR or if it's a PR from a fork. + # This prevents duplicate workflows from running on PR's that originate + # from the repository itself. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + env: + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + strategy: + # Don't cancel other runners if one fails. + fail-fast: false + matrix: + # Also try windows-2019? + os: [ macos-latest, ubuntu-latest, windows-2019 ] + defaults: + run: + # Use bash shells on all platforms. + shell: bash + steps: + - name: ๐Ÿงพ Checkout + uses: actions/checkout@v3 + + - name: ๐Ÿ’ฝ Setup .NET SDK + uses: actions/setup-dotnet@v3 + with: + # Use the .NET SDK from global.json in the root of the repository. + global-json-file: global.json + + - name: ๐Ÿ“ฆ Restore Dependencies + run: dotnet restore + + - name: ๐Ÿฆบ Build Projects + run: dotnet build + + - name: ๐Ÿ›ฃ Add Current Installation To Path + # Gets the last line of the output from the installation and adds it to the path. + working-directory: GodotEnv + run: | + # Use tool to install Godot. Last line of output is the path to the + # symlink that always points to the active version of Godot. + dotnet run -- godot install 3.5.3 + + - name: ๐Ÿค– Check Godot Location + working-directory: GodotEnv + run: | + # Get path to the symlink that always points to the active version of + # Godot. + GODOT_SYMLINK="$(dotnet run -- godot env path)" + + # Make sure we can use Godot. + $GODOT_SYMLINK --version + + echo "โœ… Godot location is in path!" + + - name: ๐ŸŒด Set GODOT System Environment Variable + working-directory: GodotEnv + run: | + dotnet run -- godot env setup + + - name: ๐Ÿงช Verify GODOT System Environment Variable + working-directory: GodotEnv + run: | + # Make sure we can retrieve environment variable on all systems. + VERIFY_GODOT=$(dotnet run -- godot env get) + echo "GODOT=$VERIFY_GODOT" + if [ -z "$VERIFY_GODOT" ]; then + echo "โŒ GODOT environment variable is empty!" + exit 1 + fi + + - name: ๐Ÿ—‘ Uninstall + working-directory: GodotEnv + run: | + echo "Before uninstall:" + dotnet run -- godot list + echo "Uninstalling..." + dotnet run -- godot uninstall 3.5.3 + echo "After uninstall:" + dotnet run -- godot list + + install_tests_4x: + name: ๐Ÿ”‹ Godot 4.x Integration Tests with ${{ matrix.os }} runs-on: ${{ matrix.os }} # Only run the workflow if it's not a PR or if it's a PR from a fork. # This prevents duplicate workflows from running on PR's that originate diff --git a/GodotEnv.Tests/src/common/utilities/LogTest.cs b/GodotEnv.Tests/src/common/utilities/LogTest.cs index ef50e70..d0e5b2d 100644 --- a/GodotEnv.Tests/src/common/utilities/LogTest.cs +++ b/GodotEnv.Tests/src/common/utilities/LogTest.cs @@ -101,7 +101,7 @@ public void OutputsCorrectStyleChanges() { [/style][style fg="green"]F [/style] - """ + """.ReplaceLineEndings() ); } diff --git a/GodotEnv.Tests/src/features/godot/models/GodotEnvironmentTest.cs b/GodotEnv.Tests/src/features/godot/models/GodotEnvironmentTest.cs index 675581c..063a03e 100644 --- a/GodotEnv.Tests/src/features/godot/models/GodotEnvironmentTest.cs +++ b/GodotEnv.Tests/src/features/godot/models/GodotEnvironmentTest.cs @@ -8,68 +8,81 @@ namespace Chickensoft.GodotEnv.Tests.features.godot.models; using Xunit; public class GodotEnvironmentTest { - private readonly SemanticVersion version; - private readonly Mock computer; - private readonly Mock fileClient; - - public GodotEnvironmentTest() { - computer = new Mock(); - fileClient = new Mock(); - version = new SemanticVersion("4", "1", "2", string.Empty); - } + private readonly Mock computer = new(); + private readonly Mock fileClient = new(); + private readonly SemanticVersion version4 = new("4", "1", "2"); + private readonly SemanticVersion version3 = new("3", "5", "3"); [Fact] public void GetsExpectedMacDownloadUrl() { var platform = new MacOS(fileClient.Object, computer.Object); - var downloadUrl = platform.GetDownloadUrl(version, false, false); - downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}4.1.2-stable/Godot_v4.1.2-stable_macos.universal.zip"); + var downloadUrl = platform.GetDownloadUrl(version4, false, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version4, "stable_macos.universal")); + + downloadUrl = platform.GetDownloadUrl(version3, false, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version3, "stable_osx.universal")); } [Fact] public void GetsExpectedMacMonoDownloadUrl() { var platform = new MacOS(fileClient.Object, computer.Object); - var downloadUrl = platform.GetDownloadUrl(version, true, false); - downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}4.1.2-stable/Godot_v4.1.2-stable_mono_macos.universal.zip"); + var downloadUrl = platform.GetDownloadUrl(version4, true, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version4, "stable_mono_macos.universal")); + + downloadUrl = platform.GetDownloadUrl(version3, true, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version3, "stable_mono_osx.universal")); } [Fact] public void GetsExpectedWindowsDownloadUrl() { var platform = new Windows(fileClient.Object, computer.Object); - var downloadUrl = platform.GetDownloadUrl(version, false, false); + var downloadUrl = platform.GetDownloadUrl(version4, false, false); downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}4.1.2-stable/Godot_v4.1.2-stable_win64.exe.zip"); + + downloadUrl = platform.GetDownloadUrl(version3, false, false); + downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}{version3.VersionString}-stable/Godot_v{version3.VersionString}-stable_win64.exe.zip"); } [Fact] public void GetsExpectedWindowsMonoDownloadUrl() { var platform = new Windows(fileClient.Object, computer.Object); - var downloadUrl = platform.GetDownloadUrl(version, true, false); - downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}4.1.2-stable/Godot_v4.1.2-stable_mono_win64.zip"); + var downloadUrl = platform.GetDownloadUrl(version4, true, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version4, "stable_mono_win64")); + + downloadUrl = platform.GetDownloadUrl(version3, true, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version3, "stable_mono_win64")); } [Fact] public void GetsExpectedLinuxDownloadUrl() { var platform = new Linux(fileClient.Object, computer.Object); - var downloadUrl = platform.GetDownloadUrl(version, false, false); - downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}4.1.2-stable/Godot_v4.1.2-stable_linux.x86_64.zip"); + var downloadUrl = platform.GetDownloadUrl(version4, false, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version4, "stable_linux.x86_64")); + + downloadUrl = platform.GetDownloadUrl(version3, false, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version3, "stable_x11.64")); } [Fact] public void GetsExpectedLinuxMonoDownloadUrl() { var platform = new Linux(fileClient.Object, computer.Object); - var downloadUrl = platform.GetDownloadUrl(version, true, false); - downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}4.1.2-stable/Godot_v4.1.2-stable_mono_linux_x86_64.zip"); + var downloadUrl = platform.GetDownloadUrl(version4, true, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version4, "stable_mono_linux_x86_64")); + + downloadUrl = platform.GetDownloadUrl(version3, true, false); + downloadUrl.ShouldBe(GetExpectedDownloadUrl(version3, "stable_mono_x11_64")); } [Fact] public void GetsExpectedTemplatesDownloadUrl() { var platform = new MacOS(fileClient.Object, computer.Object); - var downloadUrl = platform.GetDownloadUrl(version, false, true); + var downloadUrl = platform.GetDownloadUrl(version4, false, true); downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}4.1.2-stable/Godot_v4.1.2-stable_export_templates.tpz"); } @@ -77,8 +90,11 @@ public void GetsExpectedTemplatesDownloadUrl() { [Fact] public void GetsExpectedTemplatesMonoDownloadUrl() { var platform = new MacOS(fileClient.Object, computer.Object); - var downloadUrl = platform.GetDownloadUrl(version, true, true); + var downloadUrl = platform.GetDownloadUrl(version4, true, true); downloadUrl.ShouldBe($"{GodotEnvironment.GODOT_URL_PREFIX}4.1.2-stable/Godot_v4.1.2-stable_mono_export_templates.tpz"); } + + private static string GetExpectedDownloadUrl(SemanticVersion version, string platformSuffix) => + $"{GodotEnvironment.GODOT_URL_PREFIX}{version.VersionString}-stable/Godot_v{version.VersionString}-{platformSuffix}.zip"; } diff --git a/GodotEnv/src/features/godot/domain/GodotRepository.cs b/GodotEnv/src/features/godot/domain/GodotRepository.cs index 1d3c1eb..48f38fd 100644 --- a/GodotEnv/src/features/godot/domain/GodotRepository.cs +++ b/GodotEnv/src/features/godot/domain/GodotRepository.cs @@ -357,7 +357,7 @@ public async Task UpdateGodotSymlink( if (installation.IsDotnetVersion) { // Update GodotSharp symlinks var godotSharpPath = GetGodotSharpPath( - installation.Path, installation.Version + installation.Path, installation.Version, installation.IsDotnetVersion ); log.Print(""); @@ -495,10 +495,10 @@ private string GetExecutionPath( ); private string GetGodotSharpPath( - string installationPath, SemanticVersion version + string installationPath, SemanticVersion version, bool isDotnetVersion ) => FileClient.Combine( installationPath, - Platform.GetRelativeGodotSharpPath(version) + Platform.GetRelativeGodotSharpPath(version, isDotnetVersion) ); private GodotInstallation? ReadInstallation( diff --git a/GodotEnv/src/features/godot/models/GodotEnvironment.cs b/GodotEnv/src/features/godot/models/GodotEnvironment.cs index 7901c3d..27fc536 100644 --- a/GodotEnv/src/features/godot/models/GodotEnvironment.cs +++ b/GodotEnv/src/features/godot/models/GodotEnvironment.cs @@ -36,8 +36,9 @@ public interface IGodotEnvironment { /// /// True if using the .NET-enabled version of Godot, /// false otherwise. + /// Godot version. /// Godot filename suffix. - string GetInstallerNameSuffix(bool isDotnetVersion); + string GetInstallerNameSuffix(bool isDotnetVersion, SemanticVersion version); /// /// Computes the local path where the Godot export templates should be @@ -108,8 +109,9 @@ string GetRelativeExtractedExecutablePath( /// directory that is included with Godot. /// /// Godot version. + /// Dotnet version indicator. /// Path to the GodotSharp directory. - string GetRelativeGodotSharpPath(SemanticVersion version); + string GetRelativeGodotSharpPath(SemanticVersion version, bool isDotnetVersion); } public abstract class GodotEnvironment : IGodotEnvironment { @@ -145,14 +147,15 @@ protected GodotEnvironment(IFileClient fileClient, IComputer computer) { public IComputer Computer { get; } public abstract string ExportTemplatesBasePath { get; } - public abstract string GetInstallerNameSuffix(bool isDotnetVersion); + public abstract string GetInstallerNameSuffix(bool isDotnetVersion, SemanticVersion version); public abstract void Describe(ILog log); public abstract Task IsExecutable(IShell shell, IFileInfo file); public abstract string GetRelativeExtractedExecutablePath( SemanticVersion version, bool isDotnetVersion ); public abstract string GetRelativeGodotSharpPath( - SemanticVersion version + SemanticVersion version, + bool isDotnetVersion ); // TODO: Implement @@ -300,7 +303,7 @@ protected static string GetFilenameVersionString(SemanticVersion version) { // Gets the filename of the Godot installation download for the platform. private string GetInstallerFilename( SemanticVersion version, bool isDotnetVersion - ) => GetFilenameVersionString(version) + GetInstallerNameSuffix(isDotnetVersion) + + ) => GetFilenameVersionString(version) + GetInstallerNameSuffix(isDotnetVersion, version) + ".zip"; // Gets the filename of the Godot export templates installation download for diff --git a/GodotEnv/src/features/godot/models/Linux.cs b/GodotEnv/src/features/godot/models/Linux.cs index ce473ba..6f6fbfb 100644 --- a/GodotEnv/src/features/godot/models/Linux.cs +++ b/GodotEnv/src/features/godot/models/Linux.cs @@ -11,8 +11,11 @@ public Linux(IFileClient fileClient, IComputer computer) FileClient.UserDirectory, ".local/share/godot" ); - public override string GetInstallerNameSuffix(bool isDotnetVersion) => - isDotnetVersion ? "_mono_linux_x86_64" : "_linux.x86_64"; + public override string GetInstallerNameSuffix(bool isDotnetVersion, SemanticVersion version) { + var (platformName, architecture) = GetPlatformNameAndArchitecture(version); + + return isDotnetVersion ? $"_mono_{platformName}_{architecture}" : $"_{platformName}.{architecture}"; + } public override void Describe(ILog log) => log.Info("๐Ÿง Running on Linux"); @@ -20,22 +23,40 @@ public override string GetRelativeExtractedExecutablePath( SemanticVersion version, bool isDotnetVersion ) { var fsVersionString = GetFilenameVersionString(version); - var name = fsVersionString + - $"{(isDotnetVersion ? "_mono" : "")}_linux.x86_64"; + var nameSuffix = GetInstallerNameSuffix(isDotnetVersion, version); + var (platformName, architecture) = GetPlatformNameAndArchitecture(version); + + var pathSuffix = fsVersionString + + $"{(isDotnetVersion ? "_mono" : "")}_{platformName}.{architecture}"; if (isDotnetVersion) { // Dotnet version extracts to a folder, whereas the non-dotnet version // does not. - return FileClient.Combine(fsVersionString + "_mono_linux_x86_64", name); + return FileClient.Combine(fsVersionString + nameSuffix, pathSuffix); } - return name; + return pathSuffix; } public override string GetRelativeGodotSharpPath( - SemanticVersion version + SemanticVersion version, + bool isDotnetVersion ) => FileClient.Combine( - GetFilenameVersionString(version) + "_mono_linux_x86_64", - "GodotSharp/" - ); + GetFilenameVersionString(version) + GetInstallerNameSuffix(isDotnetVersion, version), + "GodotSharp/" + ); + + private static (string platformName, string architecture) GetPlatformNameAndArchitecture( + SemanticVersion version + ) { + var architecture = "x86_64"; + var platformName = "linux"; + + if (int.TryParse(version.Major, out var parsedVersion) && parsedVersion == 3) { + architecture = "64"; + platformName = "x11"; + } + + return (platformName, architecture); + } } diff --git a/GodotEnv/src/features/godot/models/MacOS.cs b/GodotEnv/src/features/godot/models/MacOS.cs index bb8e283..41857fe 100644 --- a/GodotEnv/src/features/godot/models/MacOS.cs +++ b/GodotEnv/src/features/godot/models/MacOS.cs @@ -14,8 +14,8 @@ public MacOS(IFileClient fileClient, IComputer computer) ) ); - public override string GetInstallerNameSuffix(bool isDotnetVersion) => - $"{(isDotnetVersion ? "_mono" : "")}_macos.universal"; + public override string GetInstallerNameSuffix(bool isDotnetVersion, SemanticVersion version) => + $"{(isDotnetVersion ? "_mono" : "")}_{(int.Parse(version.Major) == 3 ? "osx" : "macos")}.universal"; public override void Describe(ILog log) => log.Info("๐Ÿ Running on macOS"); @@ -24,6 +24,7 @@ public override string GetRelativeExtractedExecutablePath( ) => $"Godot{(isDotnetVersion ? "_mono" : "")}.app/Contents/MacOS/Godot"; public override string GetRelativeGodotSharpPath( - SemanticVersion version + SemanticVersion version, + bool isDotnetVersion ) => "Godot_mono.app/Contents/Resources/GodotSharp"; } diff --git a/GodotEnv/src/features/godot/models/SemanticVersion.cs b/GodotEnv/src/features/godot/models/SemanticVersion.cs index 52178df..f1d723c 100644 --- a/GodotEnv/src/features/godot/models/SemanticVersion.cs +++ b/GodotEnv/src/features/godot/models/SemanticVersion.cs @@ -4,7 +4,7 @@ namespace Chickensoft.GodotEnv.Features.Godot.Models; using System.Text.RegularExpressions; public record SemanticVersion( - string Major, string Minor, string Patch, string Label + string Major, string Minor, string Patch, string Label = "" ) { // Borrowed from https://semver.org/ diff --git a/GodotEnv/src/features/godot/models/Windows.cs b/GodotEnv/src/features/godot/models/Windows.cs index 1d71e1a..a3b6649 100644 --- a/GodotEnv/src/features/godot/models/Windows.cs +++ b/GodotEnv/src/features/godot/models/Windows.cs @@ -14,7 +14,7 @@ public Windows(IFileClient fileClient, IComputer computer) FileClient.Combine(FileClient.UserDirectory, "\\AppData\\Roaming\\Godot") ); - public override string GetInstallerNameSuffix(bool isDotnetVersion) => + public override string GetInstallerNameSuffix(bool isDotnetVersion, SemanticVersion version) => isDotnetVersion ? "_mono_win64" : "_win64.exe"; public override Task IsExecutable(IShell shell, IFileInfo file) => @@ -42,7 +42,8 @@ public override string GetRelativeExtractedExecutablePath( } public override string GetRelativeGodotSharpPath( - SemanticVersion version + SemanticVersion version, + bool isDotnetVersion ) => FileClient.Combine( GetFilenameVersionString(version) + "_mono_win64", "GodotSharp" ); diff --git a/README.md b/README.md index cbd9d94..17ed4d7 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ GodotEnv is a command-line tool that makes it easy to switch between Godot versi GodotEnv can do the following: -- โœ… Download, extract, and install Godot 4.0+ versions from the command line on Windows, macOS, and Linux (similar to tools like [NVM], [FVM], [asdf], etc. +- โœ… Download, extract, and install Godot 3.0/4.0+ versions from the command line on Windows, macOS, and Linux (similar to tools like [NVM], [FVM], [asdf], etc. - โœ… Switch the active version of Godot by updating a symlink. - โœ… Automatically setup a system `GODOT` environment variable that always points to the active version of Godot. - โœ… Install addons in a Godot project from local paths, remote git repositories, or symlinks using an easy-to-understand `addons.json` file. No more fighting with git submodules! Just run `godotenv addons install` whenever your `addons.json` file changes.