From 23000eeb9a021455e62cfe82c731d9a52f645e34 Mon Sep 17 00:00:00 2001 From: Mary Date: Sat, 22 Jan 2022 17:56:09 +0100 Subject: [PATCH] Add new release system As AppVeyor took our project down and deleted it without any comments, we are switching to GitHub Releases earlier than anticipated. This isn't the most elegant design (and I would have prefered having a release manifest in place) but this will do for now. The concept of release channel was also defined with this change. The new base version is now 1.1.x to avoid confusion with older system. Standard test CI was disabled temporarly and may be chained later as a CI job after the release job. Users are expected to redownload the emulator to be sure to be up to date. PS: If someone from AppVeyor read this, thanks again for ruining my week-end, I will be sure to NEVER recommend you to anyone. Best Regards, Mary. --- .github/workflows/build.yml | 20 ++-- .github/workflows/release.yml | 95 +++++++++++++++++++ .../Logging/Targets/FileLogTarget.cs | 2 +- Ryujinx.Common/ReleaseInformations.cs | 32 +++++++ Ryujinx.Headless.SDL2/Program.cs | 3 +- Ryujinx/Modules/Updater/Updater.cs | 67 +++++++++---- Ryujinx/Program.cs | 3 +- 7 files changed, 192 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 Ryujinx.Common/ReleaseInformations.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca1e0f1d0..3b922cf30 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,13 +3,13 @@ name: Build job on: workflow_dispatch: inputs: {} - push: - branches: [ master ] - paths-ignore: - - '.github/*' - - '.github/ISSUE_TEMPLATE/**' - - '*.yml' - - 'README.md' + #push: + # branches: [ master ] + # paths-ignore: + # - '.github/*' + # - '.github/ISSUE_TEMPLATE/**' + # - '*.yml' + # - 'README.md' pull_request: branches: [ master ] paths-ignore: @@ -59,14 +59,14 @@ jobs: - name: Clear run: dotnet clean && dotnet nuget locals all --clear - name: Build - run: dotnet build -c "${{ matrix.configuration }}" /p:Version="1.0.0" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER + run: dotnet build -c "${{ matrix.configuration }}" /p:Version="1.1.0" /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER - name: Test run: dotnet test -c "${{ matrix.configuration }}" - name: Publish Ryujinx - run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained if: github.event_name == 'pull_request' - name: Publish Ryujinx.Headless.SDL2 - run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.0.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless /p:Version="1.1.0" /p:DebugType=embedded /p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" /p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained if: github.event_name == 'pull_request' - name: Upload Ryujinx artifact uses: actions/upload-artifact@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..189a17cd9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,95 @@ +name: Release job + +on: + workflow_dispatch: + inputs: {} + push: + branches: [ master ] + paths-ignore: + - '.github/*' + - '.github/ISSUE_TEMPLATE/**' + - '*.yml' + - 'README.md' + + +jobs: + release: + runs-on: windows-latest + + env: + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + RYUJINX_BASE_VERSION: "1.1" + RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master" + RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx" + RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master" + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x + - name: Ensure NuGet Source + uses: fabriciomurta/ensure-nuget-source@v1 + - name: Clear + run: dotnet clean && dotnet nuget locals all --clear + - name: Get version info + id: version_info + run: | + echo "::set-output name=build_version::${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 7181))" + echo "::set-output name=git_short_hash::$(git rev-parse --short "${{ github.sha }}")" + shell: bash + - name: Configure for release + run: | + sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' Ryujinx.Common/ReleaseInformations.cs + sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' Ryujinx.Common/ReleaseInformations.cs + sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' Ryujinx.Common/ReleaseInformations.cs + sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' Ryujinx.Common/ReleaseInformations.cs + sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' Ryujinx.Common/ReleaseInformations.cs + shell: bash + - name: Create output dir + run: "mkdir release_output" + - name: Publish Windows + run: | + dotnet publish -c Release -r win-x64 -o ./publish_windows/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx --self-contained + dotnet publish -c Release -r win-x64 -o ./publish_windows_sdl2_headless/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained + - name: Packing Windows builds + run: | + pushd publish_windows + 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish + popd + + pushd publish_windows_sdl2_headless + 7z a ../release_output/ryujinx-headless-sdl2-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish + popd + shell: bash + + - name: Publish Linux + run: | + dotnet publish -c Release -r linux-x64 -o ./publish_linux/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx --self-contained + dotnet publish -c Release -r linux-x64 -o ./publish_linux_sdl2_headless/publish /p:Version="${{ steps.version_info.outputs.build_version }}" /p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" /p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained + + - name: Packing Linux builds + run: | + pushd publish_linux + tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish + popd + + pushd publish_linux_sdl2_headless + tar -czvf ../release_output/ryujinx-headless-sdl2-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish + popd + shell: bash + + - name: Pushing new release + uses: ncipollo/release-action@v1 + with: + name: ${{ steps.version_info.outputs.build_version }} + artifacts: "release_output/*.tar.gz,release_output/*.zip" + tag: ${{ steps.version_info.outputs.build_version }} + body: "For more informations about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)." + allowUpdates: true + removeArtifacts: true + replacesArtifacts: true + owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }} + repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }} + token: ${{ secrets.RELEASE_TOKEN }} diff --git a/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs index 5591c60b4..e83b26cdb 100644 --- a/Ryujinx.Common/Logging/Targets/FileLogTarget.cs +++ b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs @@ -30,7 +30,7 @@ namespace Ryujinx.Common.Logging files[i].Delete(); } - string version = Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion; + string version = ReleaseInformations.GetVersion(); // Get path for the current time path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.log"); diff --git a/Ryujinx.Common/ReleaseInformations.cs b/Ryujinx.Common/ReleaseInformations.cs new file mode 100644 index 000000000..1dcb4bf30 --- /dev/null +++ b/Ryujinx.Common/ReleaseInformations.cs @@ -0,0 +1,32 @@ +namespace Ryujinx.Common +{ + // DO NOT EDIT, filled by CI + public static class ReleaseInformations + { + public static string BuildVersion = "%%RYUJINX_BUILD_VERSION%%"; + public static string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%"; + public static string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%"; + public static string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%"; + public static string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%"; + + public static bool IsValid() + { + return !BuildGitHash.StartsWith("%%") && + !ReleaseChannelName.StartsWith("%%") && + !ReleaseChannelOwner.StartsWith("%%") && + !ReleaseChannelRepo.StartsWith("%%"); + } + + public static string GetVersion() + { + if (IsValid()) + { + return BuildVersion; + } + else + { + return "1.0.0-dirty"; + } + } + } +} diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs index 71487a099..14c723603 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/Ryujinx.Headless.SDL2/Program.cs @@ -3,6 +3,7 @@ using ARMeilleure.Translation.PTC; using CommandLine; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; @@ -57,7 +58,7 @@ namespace Ryujinx.Headless.SDL2 static void Main(string[] args) { - Version = Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion; + Version = ReleaseInformations.GetVersion(); Console.Title = $"Ryujinx Console {Version} (Headless SDL2)"; diff --git a/Ryujinx/Modules/Updater/Updater.cs b/Ryujinx/Modules/Updater/Updater.cs index 5ce896e5e..4a96208fa 100644 --- a/Ryujinx/Modules/Updater/Updater.cs +++ b/Ryujinx/Modules/Updater/Updater.cs @@ -3,6 +3,7 @@ using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Zip; using Newtonsoft.Json.Linq; +using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.Ui; using Ryujinx.Ui.Widgets; @@ -29,17 +30,26 @@ namespace Ryujinx.Modules private static readonly string UpdatePublishDir = Path.Combine(UpdateDir, "publish"); private static readonly int ConnectionCount = 4; - private static string _jobId; private static string _buildVer; private static string _platformExt; private static string _buildUrl; private static long _buildSize; - - private const string AppveyorApiUrl = "https://ci.appveyor.com/api"; + + private const string GitHubApiURL = "https://api.github.com"; // On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates. private static readonly string[] WindowsDependencyDirs = new string[] { "bin", "etc", "lib", "share" }; + private static HttpClient ConstructHttpClient() + { + HttpClient result = new HttpClient(); + + // Required by GitHub to interract with APIs. + result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); + + return result; + } + public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate) { if (Running) return; @@ -88,22 +98,45 @@ namespace Ryujinx.Modules return; } - // Get latest version number from Appveyor + // Get latest version number from GitHub API try { - using (HttpClient jsonClient = new HttpClient()) + using (HttpClient jsonClient = ConstructHttpClient()) { + string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformations.ReleaseChannelOwner}/{ReleaseInformations.ReleaseChannelRepo}/releases/latest"; + // Fetch latest build information - string fetchedJson = await jsonClient.GetStringAsync($"{AppveyorApiUrl}/projects/gdkchan/ryujinx/branch/master"); + string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL); JObject jsonRoot = JObject.Parse(fetchedJson); - JToken buildToken = jsonRoot["build"]; + JToken assets = jsonRoot["assets"]; - _jobId = (string)buildToken["jobs"][0]["jobId"]; - _buildVer = (string)buildToken["version"]; - _buildUrl = $"{AppveyorApiUrl}/buildjobs/{_jobId}/artifacts/ryujinx-{_buildVer}-{_platformExt}"; + _buildVer = (string)jsonRoot["name"]; - // If build not done, assume no new update are availaible. - if ((string)buildToken["jobs"][0]["status"] != "success") + foreach (JToken asset in assets) + { + string assetName = (string)asset["name"]; + string assetState = (string)asset["state"]; + string downloadURL = (string)asset["browser_download_url"]; + + if (!assetName.StartsWith("ryujinx-headless-sdl2") && assetName.EndsWith(_platformExt)) + { + _buildUrl = downloadURL; + + if (assetState != "uploaded") + { + if (showVersionUpToDate) + { + GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + } + + return; + } + + break; + } + } + + if (_buildUrl == null) { if (showVersionUpToDate) { @@ -117,7 +150,7 @@ namespace Ryujinx.Modules catch (Exception exception) { Logger.Error?.Print(LogClass.Application, exception.Message); - GtkDialog.CreateErrorDialog("An error occurred when trying to get release information from AppVeyor. This can be caused if a new release is being compiled by AppVeyor. Try again in a few minutes."); + GtkDialog.CreateErrorDialog("An error occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes."); return; } @@ -128,8 +161,8 @@ namespace Ryujinx.Modules } catch { - GtkDialog.CreateWarningDialog("Failed to convert the received Ryujinx version from AppVeyor.", "Cancelling Update!"); - Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from AppVeyor!"); + GtkDialog.CreateWarningDialog("Failed to convert the received Ryujinx version from GitHub Release.", "Cancelling Update!"); + Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from GitHub Release!"); return; } @@ -148,7 +181,7 @@ namespace Ryujinx.Modules } // Fetch build size information to learn chunk sizes. - using (HttpClient buildSizeClient = new HttpClient()) + using (HttpClient buildSizeClient = ConstructHttpClient()) { try { @@ -520,7 +553,7 @@ namespace Ryujinx.Modules return false; } - if (Program.Version.Contains("dirty")) + if (Program.Version.Contains("dirty") || !ReleaseInformations.IsValid()) { if (showWarnings) { diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 1e0fdd3af..8cd5a9969 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -1,5 +1,6 @@ using ARMeilleure.Translation.PTC; using Gtk; +using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Logging; @@ -68,7 +69,7 @@ namespace Ryujinx // Delete backup files after updating. Task.Run(Updater.CleanupUpdate); - Version = Assembly.GetEntryAssembly().GetCustomAttribute().InformationalVersion; + Version = ReleaseInformations.GetVersion(); Console.Title = $"Ryujinx Console {Version}";