From 5b86d4b7193baea3148abba6ce7c33e7188e4493 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 30 Aug 2024 21:26:11 +0900 Subject: [PATCH 1/3] Apply auto refactoring --- Program.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Program.cs b/Program.cs index 481ab5d..b86386f 100644 --- a/Program.cs +++ b/Program.cs @@ -150,6 +150,7 @@ public static void Main(string[] args) if (targetPlatform == RuntimeInfo.Platform.macOS) { string targetArch = ""; + if (args.Length > 3) { targetArch = args[3]; @@ -178,7 +179,11 @@ public static void Main(string[] args) string rid = $"{os}-{arch}"; string channel = rid == "win-x64" ? "win" : rid; - if (canGitHub) runCommand("dotnet", $"vpk download github --repoUrl {GithubRepoUrl} --token {GitHubAccessToken} --channel {channel} -o=\"{releasesPath}\"", throwIfNonZero: false, useSolutionPath: false); + if (canGitHub) + { + runCommand("dotnet", $"vpk download github --repoUrl {GithubRepoUrl} --token {GitHubAccessToken} --channel {channel} -o=\"{releasesPath}\"", throwIfNonZero: false, + useSolutionPath: false); + } if (targetPlatform == RuntimeInfo.Platform.Linux) { @@ -199,6 +204,7 @@ public static void Main(string[] args) if (targetPlatform == RuntimeInfo.Platform.Windows) { bool rcEditCommand = runCommand("tools/rcedit-x64.exe", $"\"{Path.Combine(publishDir, "osu!.exe")}\" --set-icon \"{iconPath}\"", exitOnFail: false); + if (!rcEditCommand) { // Retry again with wine @@ -236,8 +242,11 @@ public static void Main(string[] args) runCommand("dotnet", $"vpk [{os}] pack -u {PackageName} -v {version} -r {rid} -o \"{releasesPath}\" -e \"{applicationName}\" --channel={channel}{extraCmd}", useSolutionPath: false); if (canGitHub && GitHubUpload) + { runCommand("dotnet", - $"vpk upload github --repoUrl {GithubRepoUrl} --token {GitHubAccessToken} -o\"{releasesPath}\" --tag {version} --releaseName {version} --merge --channel={channel}", useSolutionPath: false); + $"vpk upload github --repoUrl {GithubRepoUrl} --token {GitHubAccessToken} -o\"{releasesPath}\" --tag {version} --releaseName {version} --merge --channel={channel}", + useSolutionPath: false); + } } else if (targetPlatform == RuntimeInfo.Platform.Android) { @@ -338,6 +347,7 @@ private static void uploadBuild(string version) Debug.Assert(targetRelease.UploadUrl != null); var assetUploadUrl = targetRelease.UploadUrl.Replace("{?name,label}", "?name={0}"); + foreach (var a in Directory.GetFiles(releases_folder).Reverse()) //reverse to upload RELEASES first. { if (Path.GetFileName(a).StartsWith('.')) @@ -442,6 +452,7 @@ private static bool runCommand(string command, string args, bool useSolutionPath p.WaitForExit(); if (p.ExitCode == 0) return true; + write(output); } catch (Exception e) From 0e07b2c628a7637d8ec7b925f78812b9a45d52ab Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 30 Aug 2024 23:21:13 +0900 Subject: [PATCH 2/3] Split into individual builders --- Builders/AndroidBuilder.cs | 47 ++++ Builders/Builder.cs | 53 +++++ Builders/IOSBuilder.cs | 28 +++ Builders/LinuxBuilder.cs | 44 ++++ Builders/MacOSBuilder.cs | 48 ++++ Builders/WindowsBuilder.cs | 62 +++++ Logger.cs | 35 +++ Program.cs | 433 +++++++--------------------------- Uploaders/GitHubUploader.cs | 68 ++++++ Uploaders/Uploader.cs | 12 + Uploaders/VelopackUploader.cs | 66 ++++++ 11 files changed, 551 insertions(+), 345 deletions(-) create mode 100644 Builders/AndroidBuilder.cs create mode 100644 Builders/Builder.cs create mode 100644 Builders/IOSBuilder.cs create mode 100644 Builders/LinuxBuilder.cs create mode 100644 Builders/MacOSBuilder.cs create mode 100644 Builders/WindowsBuilder.cs create mode 100644 Logger.cs create mode 100644 Uploaders/GitHubUploader.cs create mode 100644 Uploaders/Uploader.cs create mode 100644 Uploaders/VelopackUploader.cs diff --git a/Builders/AndroidBuilder.cs b/Builders/AndroidBuilder.cs new file mode 100644 index 0000000..9da4193 --- /dev/null +++ b/Builders/AndroidBuilder.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Desktop.Deploy.Uploaders; + +namespace osu.Desktop.Deploy.Builders +{ + public class AndroidBuilder : Builder + { + private readonly string? codeSigningPassword; + + public AndroidBuilder(string version, string? codeSigningPassword) + : base(version) + { + if (!string.IsNullOrEmpty(Program.AndroidCodeSigningCertPath)) + this.codeSigningPassword = codeSigningPassword ?? Program.ReadLineMasked("Enter code signing password: "); + } + + protected override string TargetFramework => "net8.0-android"; + protected override string RuntimeIdentifier => "android-arm64"; + + public override Uploader CreateUploader() => new GitHubUploader(); + + public override void Build() + { + string codeSigningArguments = string.Empty; + + if (!string.IsNullOrEmpty(Program.AndroidCodeSigningCertPath)) + { + codeSigningArguments += + $" -p:AndroidKeyStore=true" + + $" -p:AndroidSigningKeyStore={Program.AndroidCodeSigningCertPath}" + + $" -p:AndroidSigningKeyAlias={Path.GetFileNameWithoutExtension(Program.AndroidCodeSigningCertPath)}" + + $" -p:AndroidSigningKeyPass={codeSigningPassword}" + + $" -p:AndroidSigningKeyStorePass={codeSigningPassword}"; + } + + string[] versionParts = Version.Split('.'); + string versionCode = versionParts[0].PadLeft(4, '0') + versionParts[1].PadLeft(4, '0') + versionParts[2].PadLeft(1, '0'); + + RunDotnetPublish($"-p:ApplicationVersion={versionCode} {codeSigningArguments}"); + + File.Move(Path.Combine(Program.StagingPath, "sh.ppy.osulazer-Signed.apk"), Path.Combine(Program.ReleasesPath, "sh.ppy.osulazer.apk"), true); + } + } +} diff --git a/Builders/Builder.cs b/Builders/Builder.cs new file mode 100644 index 0000000..2ae496d --- /dev/null +++ b/Builders/Builder.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Desktop.Deploy.Uploaders; + +namespace osu.Desktop.Deploy.Builders +{ + public abstract class Builder + { + protected abstract string TargetFramework { get; } + protected abstract string RuntimeIdentifier { get; } + + protected string SplashImagePath => Path.Combine(Program.SolutionPath, "assets", "lazer-nuget.png"); + protected string IconPath => Path.Combine(Program.SolutionPath, Program.ProjectName, Program.IconName); + + protected readonly string Version; + + protected Builder(string version) + { + Version = version; + + refreshDirectory(Program.STAGING_FOLDER); + } + + public abstract Uploader CreateUploader(); + + public abstract void Build(); + + protected void RunDotnetPublish(string? extraArgs = null, string? outputDir = null) + { + extraArgs ??= string.Empty; + outputDir ??= Program.StagingPath; + + Program.RunCommand("dotnet", $"publish" + + $" -f {TargetFramework}" + + $" -r {RuntimeIdentifier}" + + $" -c Release" + + $" -o \"{outputDir}\"" + + $" -p:Version={Version}" + + $" --self-contained" + + $" {extraArgs}" + + $" {Program.ProjectName}"); + } + + private static void refreshDirectory(string directory) + { + if (Directory.Exists(directory)) + Directory.Delete(directory, true); + Directory.CreateDirectory(directory); + } + } +} diff --git a/Builders/IOSBuilder.cs b/Builders/IOSBuilder.cs new file mode 100644 index 0000000..eab5cc2 --- /dev/null +++ b/Builders/IOSBuilder.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Desktop.Deploy.Uploaders; + +namespace osu.Desktop.Deploy.Builders +{ + public class IOSBuilder : Builder + { + public IOSBuilder(string version) + : base(version) + { + } + + protected override string TargetFramework => "net8.0-ios"; + protected override string RuntimeIdentifier => "ios-arm64"; + + public override Uploader CreateUploader() => new GitHubUploader(); + + public override void Build() + { + RunDotnetPublish("-p:ApplicationDisplayVersion=1.0"); + + File.Move(Path.Combine(Program.StagingPath, "osu.iOS.app"), Path.Combine(Program.ReleasesPath, "osu.iOS.app"), true); + } + } +} diff --git a/Builders/LinuxBuilder.cs b/Builders/LinuxBuilder.cs new file mode 100644 index 0000000..3b7e79a --- /dev/null +++ b/Builders/LinuxBuilder.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Desktop.Deploy.Uploaders; + +namespace osu.Desktop.Deploy.Builders +{ + public class LinuxBuilder : Builder + { + private const string app_dir = "osu!.AppDir"; + private const string app_name = "osu!"; + private const string os_name = "linux"; + + private readonly string stagingTarget; + private readonly string publishTarget; + + public LinuxBuilder(string version) + : base(version) + { + stagingTarget = Path.Combine(Program.StagingPath, app_dir); + publishTarget = Path.Combine(stagingTarget, "usr", "bin"); + } + + protected override string TargetFramework => "net8.0"; + protected override string RuntimeIdentifier => $"{os_name}-x64"; + + public override Uploader CreateUploader() => new VelopackUploader(app_name, os_name, RuntimeIdentifier, RuntimeIdentifier, stagingPath: stagingTarget); + + public override void Build() + { + if (Directory.Exists(stagingTarget)) + Directory.Delete(stagingTarget, true); + Directory.CreateDirectory(stagingTarget); + + foreach (var file in Directory.GetFiles(Path.Combine(Program.TemplatesPath, app_dir))) + new FileInfo(file).CopyTo(Path.Combine(stagingTarget, Path.GetFileName(file))); + + Program.RunCommand("chmod", $"+x {stagingTarget}/AppRun"); + + RunDotnetPublish(outputDir: publishTarget); + } + } +} diff --git a/Builders/MacOSBuilder.cs b/Builders/MacOSBuilder.cs new file mode 100644 index 0000000..b56ee34 --- /dev/null +++ b/Builders/MacOSBuilder.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Desktop.Deploy.Uploaders; + +namespace osu.Desktop.Deploy.Builders +{ + public class MacOSBuilder : Builder + { + private const string app_name = "osu!"; + private const string os_name = "mac"; + + public MacOSBuilder(string version, string? arch) + : base(version) + { + if (string.IsNullOrEmpty(arch)) + { + Console.Write("Build for which architecture? [x64/arm64]: "); + arch = Console.ReadLine() ?? string.Empty; + } + + if (arch != "x64" && arch != "arm64") + Logger.Error($"Invalid Architecture: {arch}"); + + RuntimeIdentifier = $"{os_name}-{arch}"; + } + + protected override string TargetFramework => "net8.0"; + protected override string RuntimeIdentifier { get; } + + public override Uploader CreateUploader() + { + string extraArgs = $" --icon=\"{IconPath}\""; + + if (!string.IsNullOrEmpty(Program.AppleCodeSignCertName)) + extraArgs += $" --signAppIdentity=\"{Program.AppleCodeSignCertName}\""; + if (!string.IsNullOrEmpty(Program.AppleInstallSignCertName)) + extraArgs += $" --signInstallIdentity=\"{Program.AppleInstallSignCertName}\""; + if (!string.IsNullOrEmpty(Program.AppleNotaryProfileName)) + extraArgs += $" --notaryProfile=\"{Program.AppleNotaryProfileName}\""; + + return new VelopackUploader(app_name, os_name, RuntimeIdentifier, RuntimeIdentifier, extraArgs); + } + + public override void Build() => RunDotnetPublish(); + } +} diff --git a/Builders/WindowsBuilder.cs b/Builders/WindowsBuilder.cs new file mode 100644 index 0000000..f24c87f --- /dev/null +++ b/Builders/WindowsBuilder.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Desktop.Deploy.Uploaders; + +namespace osu.Desktop.Deploy.Builders +{ + public class WindowsBuilder : Builder + { + private const string app_name = "osu!.exe"; + private const string os_name = "win"; + private const string channel = "win"; + + private readonly string? codeSigningPassword; + + public WindowsBuilder(string version, string? codeSigningPassword) + : base(version) + { + if (!string.IsNullOrEmpty(Program.WindowsCodeSigningCertPath)) + this.codeSigningPassword = codeSigningPassword ?? Program.ReadLineMasked("Enter code signing password: "); + } + + protected override string TargetFramework => "net8.0"; + protected override string RuntimeIdentifier => $"{os_name}-x64"; + + public override Uploader CreateUploader() + { + string extraArgs = $" --splashImage=\"{SplashImagePath}\"" + + $" --icon=\"{IconPath}\""; + + if (!string.IsNullOrEmpty(Program.WindowsCodeSigningCertPath)) + extraArgs += $" --signParams=\"/td sha256 /fd sha256 /f {Program.WindowsCodeSigningCertPath} /p {codeSigningPassword} /tr http://timestamp.comodoca.com\""; + + return new VelopackUploader(app_name, os_name, RuntimeIdentifier, channel, extraArgs: extraArgs); + } + + public override void Build() + { + RunDotnetPublish(); + + bool rcEditCommand = + Program.RunCommand("tools/rcedit-x64.exe", $"\"{Path.Combine(Program.StagingPath, "osu!.exe")}\"" + + $" --set-icon \"{IconPath}\"", + exitOnFail: false); + + if (!rcEditCommand) + { + // Retry again with wine + // TODO: Should probably change this to use RuntimeInfo.OS checks instead of fail values + bool wineRcEditCommand = + Program.RunCommand("wine", $"\"{Path.GetFullPath("tools/rcedit-x64.exe")}\"" + + $" \"{Path.Combine(Program.StagingPath, "osu!.exe")}\"" + + $" --set-icon \"{IconPath}\"", + exitOnFail: false); + + if (!wineRcEditCommand) + Logger.Error("Failed to set icon on osu!.exe"); + } + } + } +} diff --git a/Logger.cs b/Logger.cs new file mode 100644 index 0000000..7124c8f --- /dev/null +++ b/Logger.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Diagnostics; + +namespace osu.Desktop.Deploy +{ + public static class Logger + { + private static readonly Stopwatch stopwatch = Stopwatch.StartNew(); + + public static void Error(string message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"FATAL ERROR: {message}"); + Console.ResetColor(); + + Program.PauseIfInteractive(); + Environment.Exit(-1); + } + + public static void Write(string message, ConsoleColor col = ConsoleColor.Gray) + { + if (stopwatch.ElapsedMilliseconds > 0) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.Write(stopwatch.ElapsedMilliseconds.ToString().PadRight(8)); + } + + Console.ForegroundColor = col; + Console.WriteLine(message); + } + } +} diff --git a/Program.cs b/Program.cs index b86386f..b4bba94 100644 --- a/Program.cs +++ b/Program.cs @@ -7,8 +7,8 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Net.Http; -using Newtonsoft.Json; +using osu.Desktop.Deploy.Builders; +using osu.Desktop.Deploy.Uploaders; using osu.Framework; using osu.Framework.IO.Network; @@ -16,57 +16,36 @@ namespace osu.Desktop.Deploy { internal static class Program { - private const string staging_folder = "staging"; - private const string templates_folder = "templates"; - private const string releases_folder = "releases"; - - public static string? GitHubAccessToken = ConfigurationManager.AppSettings["GitHubAccessToken"]; - public static bool GitHubUpload = bool.Parse(ConfigurationManager.AppSettings["GitHubUpload"] ?? "false"); - public static string? GitHubUsername = ConfigurationManager.AppSettings["GitHubUsername"]; - public static string? GitHubRepoName = ConfigurationManager.AppSettings["GitHubRepoName"]; - public static string? SolutionName = ConfigurationManager.AppSettings["SolutionName"]; - public static string? ProjectName = ConfigurationManager.AppSettings["ProjectName"]; - public static bool IncrementVersion = bool.Parse(ConfigurationManager.AppSettings["IncrementVersion"] ?? "true"); - public static string? PackageName = ConfigurationManager.AppSettings["PackageName"]; - public static string? IconName = ConfigurationManager.AppSettings["IconName"]; - - public static string? AndroidCodeSigningCertPath = ConfigurationManager.AppSettings["AndroidCodeSigningCertPath"]; - public static string? WindowsCodeSigningCertPath = ConfigurationManager.AppSettings["WindowsCodeSigningCertPath"]; - public static string? AppleCodeSignCertName = ConfigurationManager.AppSettings["AppleCodeSignCertName"]; - public static string? AppleInstallSignCertName = ConfigurationManager.AppSettings["AppleInstallSignCertName"]; - public static string? AppleNotaryProfileName = ConfigurationManager.AppSettings["AppleNotaryProfileName"]; - + public const string STAGING_FOLDER = "staging"; + public const string TEMPLATES_FOLDER = "templates"; + public const string RELEASES_FOLDER = "releases"; + + public static string StagingPath => Path.Combine(Environment.CurrentDirectory, STAGING_FOLDER); + public static string TemplatesPath => Path.Combine(Environment.CurrentDirectory, TEMPLATES_FOLDER); + public static string ReleasesPath => Path.Combine(Environment.CurrentDirectory, RELEASES_FOLDER); + + public static string SolutionName => GetConfiguration("SolutionName"); + public static string ProjectName => GetConfiguration("ProjectName"); + public static string PackageName => GetConfiguration("PackageName"); + public static string IconName => GetConfiguration("IconName"); + + public static bool GitHubUpload => bool.Parse(ConfigurationManager.AppSettings["GitHubUpload"] ?? "false"); + public static string? GitHubUsername => ConfigurationManager.AppSettings["GitHubUsername"]; + public static string? GitHubRepoName => ConfigurationManager.AppSettings["GitHubRepoName"]; + public static string? GitHubAccessToken => ConfigurationManager.AppSettings["GitHubAccessToken"]; public static string GitHubApiEndpoint => $"https://api.github.com/repos/{GitHubUsername}/{GitHubRepoName}/releases"; - public static string GithubRepoUrl => $"https://github.com/{GitHubUsername}/{GitHubRepoName}"; + public static string GitHubRepoUrl => $"https://github.com/{GitHubUsername}/{GitHubRepoName}"; + public static bool CanGitHub => !string.IsNullOrEmpty(GitHubAccessToken); - private static string? solutionPath; + public static string? WindowsCodeSigningCertPath => ConfigurationManager.AppSettings["WindowsCodeSigningCertPath"]; + public static string? AndroidCodeSigningCertPath => ConfigurationManager.AppSettings["AndroidCodeSigningCertPath"]; + public static string? AppleCodeSignCertName => ConfigurationManager.AppSettings["AppleCodeSignCertName"]; + public static string? AppleInstallSignCertName => ConfigurationManager.AppSettings["AppleInstallSignCertName"]; + public static string? AppleNotaryProfileName => ConfigurationManager.AppSettings["AppleNotaryProfileName"]; - private static string stagingPath => Path.Combine(Environment.CurrentDirectory, staging_folder); - private static string templatesPath => Path.Combine(Environment.CurrentDirectory, templates_folder); - private static string releasesPath => Path.Combine(Environment.CurrentDirectory, releases_folder); + public static bool IncrementVersion => bool.Parse(ConfigurationManager.AppSettings["IncrementVersion"] ?? "true"); - private static string iconPath - { - get - { - Debug.Assert(solutionPath != null); - Debug.Assert(ProjectName != null); - Debug.Assert(IconName != null); - - return Path.Combine(solutionPath, ProjectName, IconName); - } - } - - private static string splashImagePath - { - get - { - Debug.Assert(solutionPath != null); - return Path.Combine(solutionPath, "assets", "lazer-nuget.png"); - } - } - - private static readonly Stopwatch stopwatch = new Stopwatch(); + public static string SolutionPath { get; private set; } = null!; private static bool interactive; @@ -83,20 +62,20 @@ public static void Main(string[] args) findSolutionPath(); - if (!Directory.Exists(releases_folder)) + if (!Directory.Exists(RELEASES_FOLDER)) { - write("WARNING: No release directory found. Make sure you want this!", ConsoleColor.Yellow); - Directory.CreateDirectory(releases_folder); + Logger.Write("WARNING: No release directory found. Make sure you want this!", ConsoleColor.Yellow); + Directory.CreateDirectory(RELEASES_FOLDER); } GitHubRelease? lastRelease = null; - if (canGitHub) + if (CanGitHub) { - write("Checking GitHub releases..."); - lastRelease = getLastGithubRelease(); + Logger.Write("Checking GitHub releases..."); + lastRelease = GetLastGithubRelease(); - write(lastRelease == null + Logger.Write(lastRelease == null ? "This is the first GitHub release" : $"Last GitHub release was {lastRelease.Name}."); } @@ -123,182 +102,52 @@ public static void Main(string[] args) Console.WriteLine(); Console.Write($"Ready to deploy version {version} on platform {targetPlatform}!"); - pauseIfInteractive(); - - stopwatch.Start(); + PauseIfInteractive(); - refreshDirectory(staging_folder); + Builder builder; - Debug.Assert(solutionPath != null); - - write("Running build process..."); - - if (targetPlatform == RuntimeInfo.Platform.Windows || targetPlatform == RuntimeInfo.Platform.Linux || targetPlatform == RuntimeInfo.Platform.macOS) + switch (targetPlatform) { - var os = targetPlatform switch - { - RuntimeInfo.Platform.Windows => "win", - RuntimeInfo.Platform.macOS => "mac", - RuntimeInfo.Platform.Linux => "linux", - _ => throw new ArgumentOutOfRangeException(nameof(targetPlatform), targetPlatform, null) - }; - - var arch = "x64"; - string publishDir = stagingPath; - string extraCmd = ""; - - if (targetPlatform == RuntimeInfo.Platform.macOS) - { - string targetArch = ""; - - if (args.Length > 3) - { - targetArch = args[3]; - } - else if (interactive) - { - Console.Write("Build for which architecture? [x64/arm64]: "); - targetArch = Console.ReadLine() ?? string.Empty; - } - - if (targetArch != "x64" && targetArch != "arm64") - error($"Invalid Architecture: {targetArch}"); - - arch = targetArch; - - if (!string.IsNullOrEmpty(AppleCodeSignCertName)) - extraCmd += $" --signAppIdentity=\"{AppleCodeSignCertName}\""; - if (!string.IsNullOrEmpty(AppleInstallSignCertName)) - extraCmd += $" --signInstallIdentity=\"{AppleInstallSignCertName}\""; - if (!string.IsNullOrEmpty(AppleNotaryProfileName)) - extraCmd += $" --notaryProfile=\"{AppleNotaryProfileName}\""; - - extraCmd += $" --icon=\"{iconPath}\" -p {stagingPath}"; - } - - string rid = $"{os}-{arch}"; - string channel = rid == "win-x64" ? "win" : rid; - - if (canGitHub) - { - runCommand("dotnet", $"vpk download github --repoUrl {GithubRepoUrl} --token {GitHubAccessToken} --channel {channel} -o=\"{releasesPath}\"", throwIfNonZero: false, - useSolutionPath: false); - } - - if (targetPlatform == RuntimeInfo.Platform.Linux) - { - const string app_dir = "osu!.AppDir"; - string stagingTarget = Path.Combine(stagingPath, app_dir); - if (Directory.Exists(stagingTarget)) - Directory.Delete(stagingTarget, true); - Directory.CreateDirectory(stagingTarget); - foreach (var file in Directory.GetFiles(Path.Combine(templatesPath, app_dir))) - new FileInfo(file).CopyTo(Path.Combine(stagingTarget, Path.GetFileName(file))); - runCommand("chmod", $"+x {stagingTarget}/AppRun"); - publishDir = Path.Combine(stagingTarget, "usr/bin/"); - extraCmd += $" -p \"{stagingTarget}\""; - } + case RuntimeInfo.Platform.Windows: + builder = new WindowsBuilder(version, getArg(0)); + break; - runCommand("dotnet", $"publish -f net8.0 -r {rid} {ProjectName} -o \"{publishDir}\" --configuration Release /p:Version={version} --self-contained"); + case RuntimeInfo.Platform.Linux: + builder = new LinuxBuilder(version); + break; - if (targetPlatform == RuntimeInfo.Platform.Windows) - { - bool rcEditCommand = runCommand("tools/rcedit-x64.exe", $"\"{Path.Combine(publishDir, "osu!.exe")}\" --set-icon \"{iconPath}\"", exitOnFail: false); - - if (!rcEditCommand) - { - // Retry again with wine - // TODO: Should probably change this to use RuntimeInfo.OS checks instead of fail values - bool wineRcEditCommand = runCommand("wine", $"\"{Path.GetFullPath("tools/rcedit-x64.exe")}\" \"{Path.Combine(publishDir, "osu!.exe")}\" --set-icon \"{iconPath}\"", - exitOnFail: false); - if (!wineRcEditCommand) - error("Failed to set icon on osu!.exe"); - } - - if (!string.IsNullOrEmpty(WindowsCodeSigningCertPath)) - { - string? codeSigningPassword; - - if (args.Length > 0) - { - codeSigningPassword = args[0]; - } - else - { - Console.Write("Enter code signing password: "); - codeSigningPassword = readLineMasked(); - } - - extraCmd += string.IsNullOrEmpty(codeSigningPassword) - ? "" - : $" --signParams=\"/td sha256 /fd sha256 /f {WindowsCodeSigningCertPath} /p {codeSigningPassword} /tr http://timestamp.comodoca.com\""; - } - - extraCmd += $" --splashImage=\"{splashImagePath}\" --icon=\"{iconPath}\" -p {stagingPath}"; - } + case RuntimeInfo.Platform.macOS: + builder = new MacOSBuilder(version, getArg(3)); + break; - var applicationName = targetPlatform == RuntimeInfo.Platform.Windows ? "osu!.exe" : "osu!"; + case RuntimeInfo.Platform.iOS: + builder = new IOSBuilder(version); + break; - runCommand("dotnet", $"vpk [{os}] pack -u {PackageName} -v {version} -r {rid} -o \"{releasesPath}\" -e \"{applicationName}\" --channel={channel}{extraCmd}", useSolutionPath: false); + case RuntimeInfo.Platform.Android: + builder = new AndroidBuilder(version, getArg(0)); + break; - if (canGitHub && GitHubUpload) - { - runCommand("dotnet", - $"vpk upload github --repoUrl {GithubRepoUrl} --token {GitHubAccessToken} -o\"{releasesPath}\" --tag {version} --releaseName {version} --merge --channel={channel}", - useSolutionPath: false); - } + default: + throw new PlatformNotSupportedException(targetPlatform.ToString()); } - else if (targetPlatform == RuntimeInfo.Platform.Android) - { - string codeSigningArguments = string.Empty; - - if (!string.IsNullOrEmpty(AndroidCodeSigningCertPath)) - { - string? codeSigningPassword; - - if (args.Length > 0) - { - codeSigningPassword = args[0]; - } - else - { - Console.Write("Enter code signing password: "); - codeSigningPassword = readLineMasked(); - } - - codeSigningArguments += - $" -p:AndroidKeyStore=true -p:AndroidSigningKeyStore={AndroidCodeSigningCertPath} -p:AndroidSigningKeyAlias={Path.GetFileNameWithoutExtension(AndroidCodeSigningCertPath)} -p:AndroidSigningKeyPass={codeSigningPassword} -p:AndroidSigningKeyStorePass={codeSigningPassword}"; - } - string[] versionParts = version.Split('.'); - string versionCode = versionParts[0].PadLeft(4, '0') + versionParts[1].PadLeft(4, '0') + versionParts[2].PadLeft(1, '0'); + Uploader uploader = builder.CreateUploader(); - runCommand("dotnet", - $"publish -f net8.0-android -r android-arm64 -c Release -o \"{stagingPath}\" -p:Version={version} -p:ApplicationVersion={versionCode} {codeSigningArguments} --self-contained osu.Android/osu.Android.csproj"); + Logger.Write("Restoring previous build..."); + uploader.RestoreBuild(); - File.Move(Path.Combine(stagingPath, "sh.ppy.osulazer-Signed.apk"), Path.Combine(releasesPath, "sh.ppy.osulazer.apk"), true); - if (canGitHub && GitHubUpload) uploadBuild(version); - } - else if (targetPlatform == RuntimeInfo.Platform.iOS) - { - runCommand("dotnet", - $"publish -f net8.0-ios -r ios-arm64 {ProjectName} -o \"{stagingPath}\" -c Release -p:Version={version} -p:ApplicationDisplayVersion=1.0 --self-contained osu.iOS/osu.iOS.csproj"); + Logger.Write("Running build..."); + builder.Build(); - File.Move(Path.Combine(stagingPath, "osu.iOS.app"), Path.Combine(releasesPath, "osu.iOS.app"), true); - if (canGitHub && GitHubUpload) uploadBuild(version); - } - else - { - throw new PlatformNotSupportedException(targetPlatform.ToString()); - } + Logger.Write("Creating release..."); + uploader.PublishBuild(version); - if (canGitHub && GitHubUpload) - { + if (CanGitHub && GitHubUpload) openGitHubReleasePage(); - } - write("Done!"); - pauseIfInteractive(); + Logger.Write("Done!"); + PauseIfInteractive(); } private static void displayHeader() @@ -311,86 +160,22 @@ private static void displayHeader() Console.WriteLine(); } - private static IEnumerable getReleaseLines() - { - return File.ReadAllLines(Path.Combine(releases_folder, "RELEASES")).Select(l => new ReleaseLine(l)); - } - - private static void uploadBuild(string version) - { - write("Publishing to GitHub..."); - - var req = new JsonWebRequest($"{GitHubApiEndpoint}") - { - Method = HttpMethod.Post, - }; - - GitHubRelease? targetRelease = getLastGithubRelease(true); - - if (targetRelease == null || targetRelease.TagName != version) - { - write($"- Creating release {version}...", ConsoleColor.Yellow); - req.AddRaw(JsonConvert.SerializeObject(new GitHubRelease - { - Name = version, - Draft = true, - })); - req.AuthenticatedBlockingPerform(); - - targetRelease = req.ResponseObject; - } - else - { - write($"- Adding to existing release {version}...", ConsoleColor.Yellow); - } - - Debug.Assert(targetRelease.UploadUrl != null); - - var assetUploadUrl = targetRelease.UploadUrl.Replace("{?name,label}", "?name={0}"); - - foreach (var a in Directory.GetFiles(releases_folder).Reverse()) //reverse to upload RELEASES first. - { - if (Path.GetFileName(a).StartsWith('.')) - continue; - - write($"- Adding asset {a}...", ConsoleColor.Yellow); - var upload = new WebRequest(assetUploadUrl, Path.GetFileName(a)) - { - Method = HttpMethod.Post, - Timeout = 240000, - ContentType = "application/octet-stream", - }; - - upload.AddRaw(File.ReadAllBytes(a)); - upload.AuthenticatedBlockingPerform(); - } - } - private static void openGitHubReleasePage() { Process.Start(new ProcessStartInfo { - FileName = $"https://github.com/{GitHubUsername}/{GitHubRepoName}/releases", + FileName = GitHubApiEndpoint, UseShellExecute = true //see https://github.com/dotnet/corefx/issues/10361 }); } - private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken); - - private static GitHubRelease? getLastGithubRelease(bool includeDrafts = false) + public static GitHubRelease? GetLastGithubRelease(bool includeDrafts = false) { var req = new JsonWebRequest>($"{GitHubApiEndpoint}"); req.AuthenticatedBlockingPerform(); return req.ResponseObject.FirstOrDefault(r => includeDrafts || !r.Draft); } - private static void refreshDirectory(string directory) - { - if (Directory.Exists(directory)) - Directory.Delete(directory, true); - Directory.CreateDirectory(directory); - } - /// /// Find the base path of the active solution (git checkout location) /// @@ -403,10 +188,10 @@ private static void findSolutionPath() while (true) { - if (File.Exists(Path.Combine(path, $"{SolutionName}.sln"))) + if (File.Exists(Path.Combine(path, $"{Program.SolutionName}.sln"))) break; - if (Directory.Exists(Path.Combine(path, "osu")) && File.Exists(Path.Combine(path, "osu", $"{SolutionName}.sln"))) + if (Directory.Exists(Path.Combine(path, "osu")) && File.Exists(Path.Combine(path, "osu", $"{Program.SolutionName}.sln"))) { path = Path.Combine(path, "osu"); break; @@ -415,19 +200,17 @@ private static void findSolutionPath() path = path.Remove(path.LastIndexOf(Path.DirectorySeparatorChar)); } - path += Path.DirectorySeparatorChar; - - solutionPath = path; + SolutionPath = path + Path.DirectorySeparatorChar; } - private static bool runCommand(string command, string args, bool useSolutionPath = true, Dictionary? environmentVariables = null, bool throwIfNonZero = true, - bool exitOnFail = true) + public static bool RunCommand(string command, string args, bool useSolutionPath = true, Dictionary? environmentVariables = null, bool throwIfNonZero = true, + bool exitOnFail = true) { - write($"Running {command} {args}..."); + Logger.Write($"Running {command} {args}..."); var psi = new ProcessStartInfo(command, args) { - WorkingDirectory = useSolutionPath ? solutionPath : Environment.CurrentDirectory, + WorkingDirectory = useSolutionPath ? SolutionPath : Environment.CurrentDirectory, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, @@ -453,43 +236,36 @@ private static bool runCommand(string command, string args, bool useSolutionPath if (p.ExitCode == 0) return true; - write(output); + Logger.Write(output); } catch (Exception e) { - write(e.Message); + Logger.Write(e.Message); } if (!throwIfNonZero) return false; if (exitOnFail) - error($"Command {command} {args} failed!"); + Logger.Error($"Command {command} {args} failed!"); else - write($"Command {command} {args} failed!"); + Logger.Write($"Command {command} {args} failed!"); return false; } - private static string? readLineMasked() + public static string? ReadLineMasked(string prompt) { + Console.WriteLine(prompt); + var fg = Console.ForegroundColor; Console.ForegroundColor = Console.BackgroundColor; + var ret = Console.ReadLine(); Console.ForegroundColor = fg; return ret; } - private static void error(string message) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"FATAL ERROR: {message}"); - Console.ResetColor(); - - pauseIfInteractive(); - Environment.Exit(-1); - } - - private static void pauseIfInteractive() + public static void PauseIfInteractive() { if (interactive) Console.ReadLine(); @@ -497,52 +273,19 @@ private static void pauseIfInteractive() Console.WriteLine(); } - private static void write(string message, ConsoleColor col = ConsoleColor.Gray) - { - if (stopwatch.ElapsedMilliseconds > 0) - { - Console.ForegroundColor = ConsoleColor.Green; - Console.Write(stopwatch.ElapsedMilliseconds.ToString().PadRight(8)); - } - - Console.ForegroundColor = col; - Console.WriteLine(message); - } + public static string GetConfiguration(string key) + => ConfigurationManager.AppSettings[key] ?? throw new Exception($"Configuration key '{key}' not found."); public static void AuthenticatedBlockingPerform(this WebRequest r) { r.AddHeader("Authorization", $"token {GitHubAccessToken}"); r.Perform(); } - } - - internal class RawFileWebRequest : WebRequest - { - public RawFileWebRequest(string url) - : base(url) - { - } - - protected override string Accept => "application/octet-stream"; - } - - internal class ReleaseLine - { - public string Hash; - public string Filename; - public int Filesize; - - public ReleaseLine(string line) - { - var split = line.Split(' '); - Hash = split[0]; - Filename = split[1]; - Filesize = int.Parse(split[2]); - } - public override string ToString() + private static string? getArg(int index) { - return $"{Hash} {Filename} {Filesize}"; + string[] args = Environment.GetCommandLineArgs(); + return args.Length > ++index ? args[index] : null; } } } diff --git a/Uploaders/GitHubUploader.cs b/Uploaders/GitHubUploader.cs new file mode 100644 index 0000000..6a1c8df --- /dev/null +++ b/Uploaders/GitHubUploader.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using Newtonsoft.Json; +using osu.Framework.IO.Network; + +namespace osu.Desktop.Deploy.Uploaders +{ + public class GitHubUploader : Uploader + { + public override void RestoreBuild() + { + } + + public override void PublishBuild(string version) + { + var req = new JsonWebRequest($"{Program.GitHubApiEndpoint}") + { + Method = HttpMethod.Post, + }; + + GitHubRelease? targetRelease = Program.GetLastGithubRelease(true); + + if (targetRelease == null || targetRelease.TagName != version) + { + Logger.Write($"- Creating release {version}...", ConsoleColor.Yellow); + req.AddRaw(JsonConvert.SerializeObject(new GitHubRelease + { + Name = version, + Draft = true, + })); + req.AuthenticatedBlockingPerform(); + + targetRelease = req.ResponseObject; + } + else + { + Logger.Write($"- Adding to existing release {version}...", ConsoleColor.Yellow); + } + + Debug.Assert(targetRelease.UploadUrl != null); + + var assetUploadUrl = targetRelease.UploadUrl.Replace("{?name,label}", "?name={0}"); + + foreach (var a in Directory.GetFiles(Program.RELEASES_FOLDER).Reverse()) //reverse to upload RELEASES first. + { + if (Path.GetFileName(a).StartsWith('.')) + continue; + + Logger.Write($"- Adding asset {a}...", ConsoleColor.Yellow); + var upload = new WebRequest(assetUploadUrl, Path.GetFileName(a)) + { + Method = HttpMethod.Post, + Timeout = 240000, + ContentType = "application/octet-stream", + }; + + upload.AddRaw(File.ReadAllBytes(a)); + upload.AuthenticatedBlockingPerform(); + } + } + } +} diff --git a/Uploaders/Uploader.cs b/Uploaders/Uploader.cs new file mode 100644 index 0000000..c0ff003 --- /dev/null +++ b/Uploaders/Uploader.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Desktop.Deploy.Uploaders +{ + public abstract class Uploader + { + public abstract void RestoreBuild(); + + public abstract void PublishBuild(string version); + } +} diff --git a/Uploaders/VelopackUploader.cs b/Uploaders/VelopackUploader.cs new file mode 100644 index 0000000..3103502 --- /dev/null +++ b/Uploaders/VelopackUploader.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Desktop.Deploy.Uploaders +{ + public class VelopackUploader : Uploader + { + private readonly string applicationName; + private readonly string operatingSystemName; + private readonly string runtimeIdentifier; + private readonly string channel; + private readonly string? extraArgs; + private readonly string stagingPath; + + public VelopackUploader(string applicationName, string operatingSystemName, string runtimeIdentifier, string channel, string? extraArgs = null, string? stagingPath = null) + { + this.applicationName = applicationName; + this.operatingSystemName = operatingSystemName; + this.runtimeIdentifier = runtimeIdentifier; + this.channel = channel; + this.extraArgs = extraArgs; + this.stagingPath = stagingPath ?? Program.StagingPath; + } + + public override void RestoreBuild() + { + if (Program.CanGitHub) + { + Program.RunCommand("dotnet", $"vpk download github" + + $" --repoUrl {Program.GitHubRepoUrl}" + + $" --token {Program.GitHubAccessToken}" + + $" --channel {channel}" + + $" -o=\"{Program.ReleasesPath}\"", + throwIfNonZero: false, + useSolutionPath: false); + } + } + + public override void PublishBuild(string version) + { + Program.RunCommand("dotnet", $"vpk [{operatingSystemName}] pack" + + $" -u {Program.PackageName}" + + $" -v {version}" + + $" -r {runtimeIdentifier}" + + $" -o \"{Program.ReleasesPath}\"" + + $" -e \"{applicationName}\"" + + $" -p \"{stagingPath}\"" + + $" --channel={channel}" + + $" {extraArgs}", + useSolutionPath: false); + + if (Program.CanGitHub && Program.GitHubUpload) + { + Program.RunCommand("dotnet", $"vpk upload github" + + $" --repoUrl {Program.GitHubRepoUrl}" + + $" --token {Program.GitHubAccessToken}" + + $" -o\"{Program.ReleasesPath}\"" + + $" --tag {version}" + + $" --releaseName {version}" + + $" --merge" + + $" --channel={channel}", + useSolutionPath: false); + } + } + } +} From f3d2c88f065be92f5315964a39271350f2b03221 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 31 Aug 2024 02:39:20 +0900 Subject: [PATCH 3/3] Fully specify option names --- Uploaders/VelopackUploader.cs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Uploaders/VelopackUploader.cs b/Uploaders/VelopackUploader.cs index 3103502..3c159be 100644 --- a/Uploaders/VelopackUploader.cs +++ b/Uploaders/VelopackUploader.cs @@ -27,10 +27,10 @@ public override void RestoreBuild() if (Program.CanGitHub) { Program.RunCommand("dotnet", $"vpk download github" - + $" --repoUrl {Program.GitHubRepoUrl}" - + $" --token {Program.GitHubAccessToken}" - + $" --channel {channel}" - + $" -o=\"{Program.ReleasesPath}\"", + + $" --repoUrl=\"{Program.GitHubRepoUrl}\"" + + $" --token=\"{Program.GitHubAccessToken}\"" + + $" --channel=\"{channel}\"" + + $" --outputDir=\"{Program.ReleasesPath}\"", throwIfNonZero: false, useSolutionPath: false); } @@ -39,26 +39,26 @@ public override void RestoreBuild() public override void PublishBuild(string version) { Program.RunCommand("dotnet", $"vpk [{operatingSystemName}] pack" - + $" -u {Program.PackageName}" - + $" -v {version}" - + $" -r {runtimeIdentifier}" - + $" -o \"{Program.ReleasesPath}\"" - + $" -e \"{applicationName}\"" - + $" -p \"{stagingPath}\"" - + $" --channel={channel}" + + $" --packId=\"{Program.PackageName}\"" + + $" --packVersion=\"{version}\"" + + $" --runtime=\"{runtimeIdentifier}\"" + + $" --outputDir=\"{Program.ReleasesPath}\"" + + $" --mainExe=\"{applicationName}\"" + + $" --packDir=\"{stagingPath}\"" + + $" --channel=\"{channel}\"" + $" {extraArgs}", useSolutionPath: false); if (Program.CanGitHub && Program.GitHubUpload) { Program.RunCommand("dotnet", $"vpk upload github" - + $" --repoUrl {Program.GitHubRepoUrl}" - + $" --token {Program.GitHubAccessToken}" - + $" -o\"{Program.ReleasesPath}\"" - + $" --tag {version}" - + $" --releaseName {version}" + + $" --repoUrl=\"{Program.GitHubRepoUrl}\"" + + $" --token=\"{Program.GitHubAccessToken}\"" + + $" --outputDir=\"{Program.ReleasesPath}\"" + + $" --tag=\"{version}\"" + + $" --releaseName=\"{version}\"" + $" --merge" - + $" --channel={channel}", + + $" --channel=\"{channel}\"", useSolutionPath: false); } }