mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-05 06:29:41 +00:00
Migrated build script to C# (#401)
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, deb) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, deb) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
This commit is contained in:
1 parent
3fc15d9789
commit
c715b38f41
20 files changed
+1007
-411
No files matched your search
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RootNamespace>Build</RootNamespace>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>build</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cocona" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SharedTools\SharedTools.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,21 @@
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
namespace Build.Commands;
|
||||
|
||||
public sealed class CheckRidsCommand
|
||||
{
|
||||
[Command("check-rids", Description = "Check the RIDs for the current OS")]
|
||||
public void GetRids()
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
return;
|
||||
|
||||
var rids = Environment.GetRidsForCurrentOS();
|
||||
Console.WriteLine("The following RIDs are available for the current OS:");
|
||||
foreach (var rid in rids)
|
||||
{
|
||||
Console.WriteLine($"- {rid}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using SharedTools;
|
||||
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
namespace Build.Commands;
|
||||
|
||||
public sealed partial class CollectI18NKeysCommand
|
||||
{
|
||||
[Command("collect-i18n", Description = "Collect I18N keys")]
|
||||
public async Task CollectI18NKeys()
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
return;
|
||||
|
||||
Console.WriteLine("=========================");
|
||||
Console.Write("- Collecting I18N keys ...");
|
||||
|
||||
var cwd = Environment.GetAIStudioDirectory();
|
||||
var binPath = Path.Join(cwd, "bin");
|
||||
var objPath = Path.Join(cwd, "obj");
|
||||
var wwwrootPath = Path.Join(cwd, "wwwroot");
|
||||
var allFiles = Directory.EnumerateFiles(cwd, "*", SearchOption.AllDirectories);
|
||||
var counter = 0;
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var filePath in allFiles)
|
||||
{
|
||||
counter++;
|
||||
if(filePath.StartsWith(binPath, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
if(filePath.StartsWith(objPath, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
if(filePath.StartsWith(wwwrootPath, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var content = await File.ReadAllTextAsync(filePath, Encoding.UTF8);
|
||||
var matches = this.FindAllTextTags(content);
|
||||
if (matches.Count == 0)
|
||||
continue;
|
||||
|
||||
var ns = this.DetermineNamespace(filePath);
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
var name = fileInfo.Name.Replace(fileInfo.Extension, string.Empty);
|
||||
var langNamespace = $"{ns}::{name}".ToUpperInvariant().Replace(".", "::");
|
||||
foreach (var match in matches)
|
||||
{
|
||||
var key = $"root::{langNamespace}::T{match.ToFNV32()}";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($" {counter:###,###} files processed.");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private List<string> FindAllTextTags(ReadOnlySpan<char> fileContent)
|
||||
{
|
||||
const string START_TAG = """
|
||||
T("
|
||||
""";
|
||||
|
||||
const string END_TAG = """
|
||||
")
|
||||
""";
|
||||
|
||||
var matches = new List<string>();
|
||||
var startIdx = fileContent.IndexOf(START_TAG);
|
||||
var content = fileContent;
|
||||
while (startIdx > -1)
|
||||
{
|
||||
content = content[(startIdx + START_TAG.Length)..];
|
||||
var endIdx = content.IndexOf(END_TAG);
|
||||
if (endIdx == -1)
|
||||
break;
|
||||
|
||||
var match = content[..endIdx];
|
||||
matches.Add(match.ToString());
|
||||
|
||||
startIdx = content.IndexOf(START_TAG);
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
private string? DetermineNamespace(string filePath)
|
||||
{
|
||||
// Is it a C# file? Then we can read the namespace from it:
|
||||
if (filePath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
|
||||
return this.ReadNamespaceFromCSharp(filePath);
|
||||
|
||||
// Is it a Razor file? Then, it depends:
|
||||
if (filePath.EndsWith(".razor", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Check if the file contains a namespace declaration:
|
||||
var blazorNamespace = this.ReadNamespaceFromRazor(filePath);
|
||||
if (blazorNamespace != null)
|
||||
return blazorNamespace;
|
||||
|
||||
// Alright, no namespace declaration. Let's check the corresponding C# file:
|
||||
var csFilePath = $"{filePath}.cs";
|
||||
if (File.Exists(csFilePath))
|
||||
{
|
||||
var csNamespace = this.ReadNamespaceFromCSharp(csFilePath);
|
||||
if (csNamespace != null)
|
||||
return csNamespace;
|
||||
|
||||
Console.WriteLine($"- Error: Neither the blazor file '{filePath}' nor the corresponding C# file '{csFilePath}' contain a namespace declaration.");
|
||||
return null;
|
||||
}
|
||||
|
||||
Console.WriteLine($"- Error: The blazor file '{filePath}' does not contain a namespace declaration and the corresponding C# file '{csFilePath}' does not exist.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Not a C# or Razor file. We can't determine the namespace:
|
||||
Console.WriteLine($"- Error: The file '{filePath}' is neither a C# nor a Razor file. We can't determine the namespace.");
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ReadNamespaceFromCSharp(string filePath)
|
||||
{
|
||||
var content = File.ReadAllText(filePath, Encoding.UTF8);
|
||||
var matches = CSharpNamespaceRegex().Matches(content);
|
||||
|
||||
if (matches.Count == 0)
|
||||
return null;
|
||||
|
||||
if (matches.Count > 1)
|
||||
{
|
||||
Console.WriteLine($"The file '{filePath}' contains multiple namespaces. This scenario is not supported.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = matches[0];
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
private string? ReadNamespaceFromRazor(string filePath)
|
||||
{
|
||||
var content = File.ReadAllText(filePath, Encoding.UTF8);
|
||||
var matches = BlazorNamespaceRegex().Matches(content);
|
||||
|
||||
if (matches.Count == 0)
|
||||
return null;
|
||||
|
||||
if (matches.Count > 1)
|
||||
{
|
||||
Console.WriteLine($"The file '{filePath}' contains multiple namespaces. This scenario is not supported.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = matches[0];
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
[GeneratedRegex("""@namespace\s+([a-zA-Z0-9_.]+)""")]
|
||||
private static partial Regex BlazorNamespaceRegex();
|
||||
|
||||
[GeneratedRegex("""namespace\s+([a-zA-Z0-9_.]+)""")]
|
||||
private static partial Regex CSharpNamespaceRegex();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Build.Commands;
|
||||
|
||||
public enum PrepareAction
|
||||
{
|
||||
NONE,
|
||||
|
||||
PATCH,
|
||||
MINOR,
|
||||
MAJOR,
|
||||
}
|
||||
@@ -0,0 +1,557 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Build.Tools;
|
||||
|
||||
namespace Build.Commands;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
public sealed partial class UpdateMetadataCommands
|
||||
{
|
||||
[Command("release", Description = "Prepare & build the next release")]
|
||||
public async Task Release(PrepareAction action)
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
return;
|
||||
|
||||
// Prepare the metadata for the next release:
|
||||
await this.Prepare(action);
|
||||
|
||||
// Build once to allow the Rust compiler to read the changed metadata
|
||||
// and to update all .NET artifacts:
|
||||
await this.Build();
|
||||
|
||||
// Now, we update the web assets (which may were updated by the first build):
|
||||
new UpdateWebAssetsCommand().UpdateWebAssets();
|
||||
|
||||
// Collect the I18N keys from the source code. This step yields a I18N file
|
||||
// that must be part of the final release:
|
||||
await new CollectI18NKeysCommand().CollectI18NKeys();
|
||||
|
||||
// Build the final release, where Rust knows the updated metadata, the .NET
|
||||
// artifacts are already in place, and .NET knows the updated web assets, etc.:
|
||||
await this.Build();
|
||||
}
|
||||
|
||||
[Command("prepare", Description = "Prepare the metadata for the next release")]
|
||||
public async Task Prepare(PrepareAction action)
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
return;
|
||||
|
||||
Console.WriteLine("==============================");
|
||||
Console.WriteLine("- Prepare the metadata for the next release ...");
|
||||
|
||||
var appVersion = await this.UpdateAppVersion(action);
|
||||
if (!string.IsNullOrWhiteSpace(appVersion))
|
||||
{
|
||||
var buildNumber = await this.IncreaseBuildNumber();
|
||||
var buildTime = await this.UpdateBuildTime();
|
||||
await this.UpdateChangelog(buildNumber, appVersion, buildTime);
|
||||
await this.UpdateDotnetVersion();
|
||||
await this.UpdateRustVersion();
|
||||
await this.UpdateMudBlazorVersion();
|
||||
await this.UpdateTauriVersion();
|
||||
await this.UpdateProjectCommitHash();
|
||||
await this.UpdateLicenceYear(Path.GetFullPath(Path.Combine(Environment.GetAIStudioDirectory(), "..", "..", "LICENSE.md")));
|
||||
await this.UpdateLicenceYear(Path.GetFullPath(Path.Combine(Environment.GetAIStudioDirectory(), "Pages", "About.razor.cs")));
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
[Command("build", Description = "Build MindWork AI Studio")]
|
||||
public async Task Build()
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
return;
|
||||
|
||||
//
|
||||
// Build the .NET project:
|
||||
//
|
||||
var pathApp = Environment.GetAIStudioDirectory();
|
||||
var rids = Environment.GetRidsForCurrentOS();
|
||||
foreach (var rid in rids)
|
||||
{
|
||||
Console.WriteLine("==============================");
|
||||
Console.Write($"- Start .NET build for '{rid.ToName()}' ...");
|
||||
await this.ReadCommandOutput(pathApp, "dotnet", $"clean --configuration release --runtime {rid.ToName()}");
|
||||
var dotnetBuildOutput = await this.ReadCommandOutput(pathApp, "dotnet", $"publish --configuration release --runtime {rid.ToName()} --disable-build-servers --force");
|
||||
var dotnetBuildOutputLines = dotnetBuildOutput.Split([global::System.Environment.NewLine], StringSplitOptions.RemoveEmptyEntries);
|
||||
var foundIssue = false;
|
||||
foreach (var buildOutputLine in dotnetBuildOutputLines)
|
||||
{
|
||||
if(buildOutputLine.Contains(" error ") || buildOutputLine.Contains("#warning"))
|
||||
{
|
||||
if(!foundIssue)
|
||||
{
|
||||
foundIssue = true;
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("- Build has issues:");
|
||||
}
|
||||
|
||||
Console.Write(" - ");
|
||||
Console.WriteLine(buildOutputLine);
|
||||
}
|
||||
}
|
||||
|
||||
if(foundIssue)
|
||||
Console.WriteLine();
|
||||
else
|
||||
{
|
||||
Console.WriteLine(" completed successfully.");
|
||||
}
|
||||
|
||||
//
|
||||
// Prepare the .NET artifact to be used by Tauri as sidecar:
|
||||
//
|
||||
var os = Environment.GetOS();
|
||||
var tauriSidecarArtifactName = rid switch
|
||||
{
|
||||
RID.WIN_X64 => "mindworkAIStudioServer-x86_64-pc-windows-msvc.exe",
|
||||
RID.WIN_ARM64 => "mindworkAIStudioServer-aarch64-pc-windows-msvc.exe",
|
||||
|
||||
RID.LINUX_X64 => "mindworkAIStudioServer-x86_64-unknown-linux-gnu",
|
||||
RID.LINUX_ARM64 => "mindworkAIStudioServer-aarch64-unknown-linux-gnu",
|
||||
|
||||
RID.OSX_ARM64 => "mindworkAIStudioServer-aarch64-apple-darwin",
|
||||
RID.OSX_X64 => "mindworkAIStudioServer-x86_64-apple-darwin",
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tauriSidecarArtifactName))
|
||||
{
|
||||
Console.WriteLine($"- Error: Unsupported rid '{rid.ToName()}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
var dotnetArtifactPath = Path.Combine(pathApp, "bin", "dist");
|
||||
if(!Directory.Exists(dotnetArtifactPath))
|
||||
Directory.CreateDirectory(dotnetArtifactPath);
|
||||
|
||||
var dotnetArtifactFilename = os switch
|
||||
{
|
||||
"windows" => "mindworkAIStudio.exe",
|
||||
_ => "mindworkAIStudio",
|
||||
};
|
||||
|
||||
var dotnetPublishedPath = Path.Combine(pathApp, "bin", "release", Environment.DOTNET_VERSION, rid.ToName(), "publish", dotnetArtifactFilename);
|
||||
var finalDestination = Path.Combine(dotnetArtifactPath, tauriSidecarArtifactName);
|
||||
|
||||
if(File.Exists(dotnetPublishedPath))
|
||||
Console.WriteLine("- Published .NET artifact found.");
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"- Error: Published .NET artifact not found: '{dotnetPublishedPath}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Write($"- Move the .NET artifact to the Tauri sidecar destination ...");
|
||||
try
|
||||
{
|
||||
File.Move(dotnetPublishedPath, finalDestination, true);
|
||||
Console.WriteLine(" done.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(" failed.");
|
||||
Console.WriteLine($" - Error: {e.Message}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
//
|
||||
// Build the Rust project / runtime:
|
||||
//
|
||||
|
||||
Console.WriteLine("==============================");
|
||||
Console.WriteLine("- Start building the Rust runtime ...");
|
||||
|
||||
var pathRuntime = Environment.GetRustRuntimeDirectory();
|
||||
var rustBuildOutput = await this.ReadCommandOutput(pathRuntime, "cargo", "tauri build --bundles none", true);
|
||||
var rustBuildOutputLines = rustBuildOutput.Split([global::System.Environment.NewLine], StringSplitOptions.RemoveEmptyEntries);
|
||||
var foundRustIssue = false;
|
||||
foreach (var buildOutputLine in rustBuildOutputLines)
|
||||
{
|
||||
if(buildOutputLine.Contains("error", StringComparison.OrdinalIgnoreCase) || buildOutputLine.Contains("warning"))
|
||||
{
|
||||
if(!foundRustIssue)
|
||||
{
|
||||
foundRustIssue = true;
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("- Build has issues:");
|
||||
}
|
||||
|
||||
Console.Write(" - ");
|
||||
Console.WriteLine(buildOutputLine);
|
||||
}
|
||||
}
|
||||
|
||||
if(foundRustIssue)
|
||||
Console.WriteLine();
|
||||
else
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("- Compilation completed successfully.");
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateChangelog(int buildNumber, string appVersion, string buildTime)
|
||||
{
|
||||
var pathChangelogs = Path.Combine(Environment.GetAIStudioDirectory(), "wwwroot", "changelog");
|
||||
var expectedLogFilename = $"v{appVersion}.md";
|
||||
var expectedLogFilePath = Path.Combine(pathChangelogs, expectedLogFilename);
|
||||
|
||||
if(!File.Exists(expectedLogFilePath))
|
||||
{
|
||||
Console.WriteLine($"- Error: The changelog file '{expectedLogFilename}' does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Right now, the build time is formatted as "yyyy-MM-dd HH:mm:ss UTC", but must remove the seconds:
|
||||
buildTime = buildTime[..^7] + " UTC";
|
||||
|
||||
const string CODE_START =
|
||||
"""
|
||||
LOGS =
|
||||
[
|
||||
""";
|
||||
|
||||
var changelogCodePath = Path.Join(Environment.GetAIStudioDirectory(), "Components", "Changelog.Logs.cs");
|
||||
var changelogCode = await File.ReadAllTextAsync(changelogCodePath, Encoding.UTF8);
|
||||
var updatedCode =
|
||||
$"""
|
||||
{CODE_START}
|
||||
new ({buildNumber}, "v{appVersion}, build {buildNumber} ({buildTime})", "{expectedLogFilename}"),
|
||||
""";
|
||||
|
||||
changelogCode = changelogCode.Replace(CODE_START, updatedCode);
|
||||
await File.WriteAllTextAsync(changelogCodePath, changelogCode, Environment.UTF8_NO_BOM);
|
||||
}
|
||||
|
||||
private async Task UpdateProjectCommitHash()
|
||||
{
|
||||
const int COMMIT_HASH_INDEX = 8;
|
||||
|
||||
var pathMetadata = Environment.GetMetadataPath();
|
||||
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
|
||||
var currentCommitHash = lines[COMMIT_HASH_INDEX].Trim();
|
||||
var headCommitHash = await this.ReadCommandOutput(Environment.GetAIStudioDirectory(), "git", "rev-parse HEAD");
|
||||
var first10Chars = headCommitHash[..11];
|
||||
var updatedCommitHash = $"{first10Chars}, release";
|
||||
|
||||
Console.WriteLine($"- Updating commit hash from '{currentCommitHash}' to '{updatedCommitHash}'.");
|
||||
lines[COMMIT_HASH_INDEX] = updatedCommitHash;
|
||||
|
||||
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
|
||||
}
|
||||
|
||||
private async Task<string> UpdateAppVersion(PrepareAction action)
|
||||
{
|
||||
const int APP_VERSION_INDEX = 0;
|
||||
|
||||
if (action == PrepareAction.NONE)
|
||||
{
|
||||
Console.WriteLine("- No action specified. Skipping app version update.");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var pathMetadata = Environment.GetMetadataPath();
|
||||
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
|
||||
var currentAppVersionLine = lines[APP_VERSION_INDEX].Trim();
|
||||
var currentAppVersion = AppVersionRegex().Match(currentAppVersionLine);
|
||||
var currentPatch = int.Parse(currentAppVersion.Groups["patch"].Value);
|
||||
var currentMinor = int.Parse(currentAppVersion.Groups["minor"].Value);
|
||||
var currentMajor = int.Parse(currentAppVersion.Groups["major"].Value);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case PrepareAction.PATCH:
|
||||
currentPatch++;
|
||||
break;
|
||||
|
||||
case PrepareAction.MINOR:
|
||||
currentPatch = 0;
|
||||
currentMinor++;
|
||||
break;
|
||||
|
||||
case PrepareAction.MAJOR:
|
||||
currentPatch = 0;
|
||||
currentMinor = 0;
|
||||
currentMajor++;
|
||||
break;
|
||||
}
|
||||
|
||||
var updatedAppVersion = $"{currentMajor}.{currentMinor}.{currentPatch}";
|
||||
Console.WriteLine($"- Updating app version from '{currentAppVersionLine}' to '{updatedAppVersion}'.");
|
||||
|
||||
lines[APP_VERSION_INDEX] = updatedAppVersion;
|
||||
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
|
||||
return updatedAppVersion;
|
||||
}
|
||||
|
||||
private async Task UpdateLicenceYear(string licenceFilePath)
|
||||
{
|
||||
var currentYear = DateTime.UtcNow.Year.ToString();
|
||||
var lines = await File.ReadAllLinesAsync(licenceFilePath, Encoding.UTF8);
|
||||
|
||||
var found = false;
|
||||
var copyrightYear = string.Empty;
|
||||
var updatedLines = new List<string>(lines.Length);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = FindCopyrightRegex().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
copyrightYear = match.Groups["year"].Value;
|
||||
|
||||
if(!found && copyrightYear != currentYear)
|
||||
Console.WriteLine($"- Updating the licence's year in '{Path.GetFileName(licenceFilePath)}' from '{copyrightYear}' to '{currentYear}'.");
|
||||
|
||||
updatedLines.Add(ReplaceCopyrightYearRegex().Replace(line, currentYear));
|
||||
found = true;
|
||||
}
|
||||
else
|
||||
updatedLines.Add(line);
|
||||
}
|
||||
|
||||
await File.WriteAllLinesAsync(licenceFilePath, updatedLines, Environment.UTF8_NO_BOM);
|
||||
if (!found)
|
||||
Console.WriteLine($"- Error: No copyright year found in '{Path.GetFileName(licenceFilePath)}'.");
|
||||
else if (copyrightYear == currentYear)
|
||||
Console.WriteLine($"- The copyright year in '{Path.GetFileName(licenceFilePath)}' is already up to date.");
|
||||
}
|
||||
|
||||
private async Task UpdateTauriVersion()
|
||||
{
|
||||
const int TAURI_VERSION_INDEX = 7;
|
||||
|
||||
var pathMetadata = Environment.GetMetadataPath();
|
||||
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
|
||||
var currentTauriVersion = lines[TAURI_VERSION_INDEX].Trim();
|
||||
|
||||
var matches = await this.DetermineVersion("Tauri", Environment.GetRustRuntimeDirectory(), TauriVersionRegex(), "cargo", "tree --depth 1");
|
||||
if (matches.Count == 0)
|
||||
return;
|
||||
|
||||
var updatedTauriVersion = matches[0].Groups["version"].Value;
|
||||
if(currentTauriVersion == updatedTauriVersion)
|
||||
{
|
||||
Console.WriteLine("- The Tauri version is already up to date.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"- Updated Tauri version from {currentTauriVersion} to {updatedTauriVersion}.");
|
||||
lines[TAURI_VERSION_INDEX] = updatedTauriVersion;
|
||||
|
||||
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
|
||||
}
|
||||
|
||||
private async Task UpdateMudBlazorVersion()
|
||||
{
|
||||
const int MUD_BLAZOR_VERSION_INDEX = 6;
|
||||
|
||||
var pathMetadata = Environment.GetMetadataPath();
|
||||
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
|
||||
var currentMudBlazorVersion = lines[MUD_BLAZOR_VERSION_INDEX].Trim();
|
||||
|
||||
var matches = await this.DetermineVersion("MudBlazor", Environment.GetAIStudioDirectory(), MudBlazorVersionRegex(), "dotnet", "list package");
|
||||
if (matches.Count == 0)
|
||||
return;
|
||||
|
||||
var updatedMudBlazorVersion = matches[0].Groups["version"].Value;
|
||||
if(currentMudBlazorVersion == updatedMudBlazorVersion)
|
||||
{
|
||||
Console.WriteLine("- The MudBlazor version is already up to date.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"- Updated MudBlazor version from {currentMudBlazorVersion} to {updatedMudBlazorVersion}.");
|
||||
lines[MUD_BLAZOR_VERSION_INDEX] = updatedMudBlazorVersion;
|
||||
|
||||
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
|
||||
}
|
||||
|
||||
private async Task UpdateRustVersion()
|
||||
{
|
||||
const int RUST_VERSION_INDEX = 5;
|
||||
|
||||
var pathMetadata = Environment.GetMetadataPath();
|
||||
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
|
||||
var currentRustVersion = lines[RUST_VERSION_INDEX].Trim();
|
||||
var matches = await this.DetermineVersion("Rust", Environment.GetRustRuntimeDirectory(), RustVersionRegex(), "rustc", "-Vv");
|
||||
if (matches.Count == 0)
|
||||
return;
|
||||
|
||||
var updatedRustVersion = matches[0].Groups["version"].Value + " (commit " + matches[0].Groups["commit"].Value + ")";
|
||||
if(currentRustVersion == updatedRustVersion)
|
||||
{
|
||||
Console.WriteLine("- Rust version is already up to date.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"- Updated Rust version from {currentRustVersion} to {updatedRustVersion}.");
|
||||
lines[RUST_VERSION_INDEX] = updatedRustVersion;
|
||||
|
||||
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
|
||||
}
|
||||
|
||||
private async Task UpdateDotnetVersion()
|
||||
{
|
||||
const int DOTNET_VERSION_INDEX = 4;
|
||||
const int DOTNET_SDK_VERSION_INDEX = 3;
|
||||
|
||||
var pathMetadata = Environment.GetMetadataPath();
|
||||
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
|
||||
var currentDotnetVersion = lines[DOTNET_VERSION_INDEX].Trim();
|
||||
var currentDotnetSdkVersion = lines[DOTNET_SDK_VERSION_INDEX].Trim();
|
||||
|
||||
var matches = await this.DetermineVersion(".NET", Environment.GetAIStudioDirectory(), DotnetVersionRegex(), "dotnet", "--info");
|
||||
if (matches.Count == 0)
|
||||
return;
|
||||
|
||||
var updatedDotnetVersion = matches[0].Groups["hostVersion"].Value + " (commit " + matches[0].Groups["hostCommit"].Value + ")";
|
||||
var updatedDotnetSdkVersion = matches[0].Groups["sdkVersion"].Value + " (commit " + matches[0].Groups["sdkCommit"].Value + ")";
|
||||
if(currentDotnetVersion == updatedDotnetVersion && currentDotnetSdkVersion == updatedDotnetSdkVersion)
|
||||
{
|
||||
Console.WriteLine("- .NET version is already up to date.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"- Updated .NET SDK version from {currentDotnetSdkVersion} to {updatedDotnetSdkVersion}.");
|
||||
Console.WriteLine($"- Updated .NET version from {currentDotnetVersion} to {updatedDotnetVersion}.");
|
||||
|
||||
lines[DOTNET_VERSION_INDEX] = updatedDotnetVersion;
|
||||
lines[DOTNET_SDK_VERSION_INDEX] = updatedDotnetSdkVersion;
|
||||
|
||||
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
|
||||
}
|
||||
|
||||
private async Task<IList<Match>> DetermineVersion(string name, string workingDirectory, Regex regex, string program, string command)
|
||||
{
|
||||
var processInfo = new ProcessStartInfo
|
||||
{
|
||||
WorkingDirectory = workingDirectory,
|
||||
FileName = program,
|
||||
Arguments = command,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = new Process();
|
||||
process.StartInfo = processInfo;
|
||||
process.Start();
|
||||
|
||||
var output = await process.StandardOutput.ReadToEndAsync();
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
var matches = regex.Matches(output);
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
Console.WriteLine($"- Error: Was not able to determine the {name} version.");
|
||||
return [];
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
private async Task<string> ReadCommandOutput(string workingDirectory, string program, string command, bool showLiveOutput = false)
|
||||
{
|
||||
var processInfo = new ProcessStartInfo
|
||||
{
|
||||
WorkingDirectory = workingDirectory,
|
||||
FileName = program,
|
||||
Arguments = command,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
|
||||
var sb = new StringBuilder();
|
||||
using var process = new Process();
|
||||
process.StartInfo = processInfo;
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
process.OutputDataReceived += (_, args) =>
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(args.Data))
|
||||
{
|
||||
if(showLiveOutput)
|
||||
Console.WriteLine(args.Data);
|
||||
sb.AppendLine(args.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (_, args) =>
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(args.Data))
|
||||
{
|
||||
if(showLiveOutput)
|
||||
Console.WriteLine(args.Data);
|
||||
sb.AppendLine(args.Data);
|
||||
}
|
||||
};
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private async Task<int> IncreaseBuildNumber()
|
||||
{
|
||||
const int BUILD_NUMBER_INDEX = 2;
|
||||
var pathMetadata = Environment.GetMetadataPath();
|
||||
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
|
||||
var buildNumber = int.Parse(lines[BUILD_NUMBER_INDEX]) + 1;
|
||||
|
||||
Console.WriteLine($"- Updating build number from '{lines[BUILD_NUMBER_INDEX]}' to '{buildNumber}'.");
|
||||
|
||||
lines[BUILD_NUMBER_INDEX] = buildNumber.ToString();
|
||||
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
|
||||
return buildNumber;
|
||||
}
|
||||
|
||||
private async Task<string> UpdateBuildTime()
|
||||
{
|
||||
const int BUILD_TIME_INDEX = 1;
|
||||
var pathMetadata = Environment.GetMetadataPath();
|
||||
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
|
||||
var buildTime = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + " UTC";
|
||||
|
||||
Console.WriteLine($"- Updating build time from '{lines[BUILD_TIME_INDEX]}' to '{buildTime}'.");
|
||||
|
||||
lines[BUILD_TIME_INDEX] = buildTime;
|
||||
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
|
||||
return buildTime;
|
||||
}
|
||||
|
||||
[GeneratedRegex("""(?ms).?(NET\s+SDK|SDK\s+\.NET)\s*:\s+Version:\s+(?<sdkVersion>[0-9.]+).+Commit:\s+(?<sdkCommit>[a-zA-Z0-9]+).+Host:\s+Version:\s+(?<hostVersion>[0-9.]+).+Commit:\s+(?<hostCommit>[a-zA-Z0-9]+)""")]
|
||||
private static partial Regex DotnetVersionRegex();
|
||||
|
||||
[GeneratedRegex("""rustc (?<version>[0-9.]+)(?:-nightly)? \((?<commit>[a-zA-Z0-9]+)""")]
|
||||
private static partial Regex RustVersionRegex();
|
||||
|
||||
[GeneratedRegex("""MudBlazor\s+(?<version>[0-9.]+)""")]
|
||||
private static partial Regex MudBlazorVersionRegex();
|
||||
|
||||
[GeneratedRegex("""tauri\s+v(?<version>[0-9.]+)""")]
|
||||
private static partial Regex TauriVersionRegex();
|
||||
|
||||
[GeneratedRegex("""^\s*Copyright\s+(?<year>[0-9]{4})""")]
|
||||
private static partial Regex FindCopyrightRegex();
|
||||
|
||||
[GeneratedRegex("""([0-9]{4})""")]
|
||||
private static partial Regex ReplaceCopyrightYearRegex();
|
||||
|
||||
[GeneratedRegex("""(?<major>[0-9]+)\.(?<minor>[0-9]+)\.(?<patch>[0-9]+)""")]
|
||||
private static partial Regex AppVersionRegex();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
using Build.Tools;
|
||||
|
||||
namespace Build.Commands;
|
||||
|
||||
public sealed class UpdateWebAssetsCommand
|
||||
{
|
||||
[Command("update-web", Description = "Update web assets")]
|
||||
public void UpdateWebAssets()
|
||||
{
|
||||
if(!Environment.IsWorkingDirectoryValid())
|
||||
return;
|
||||
|
||||
Console.WriteLine("=========================");
|
||||
Console.Write("- Updating web assets ...");
|
||||
|
||||
var rid = Environment.GetRidsForCurrentOS().First();
|
||||
var cwd = Environment.GetAIStudioDirectory();
|
||||
var contentPath = Path.Join(cwd, "bin", "release", Environment.DOTNET_VERSION, rid.ToName(), "publish", "wwwroot", "_content");
|
||||
var isMudBlazorDirectoryPresent = Directory.Exists(Path.Join(contentPath, "MudBlazor"));
|
||||
if (!isMudBlazorDirectoryPresent)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"- Error: No web assets found for RID '{rid}'. Please publish the project first.");
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.Join(cwd, "wwwroot", "system"));
|
||||
var sourcePaths = Directory.EnumerateFiles(contentPath, "*", SearchOption.AllDirectories);
|
||||
var counter = 0;
|
||||
foreach(var sourcePath in sourcePaths)
|
||||
{
|
||||
counter++;
|
||||
var relativePath = Path.GetRelativePath(cwd, sourcePath);
|
||||
var targetPath = Path.Join(cwd, "wwwroot", relativePath);
|
||||
var targetDirectory = Path.GetDirectoryName(targetPath);
|
||||
if (targetDirectory != null)
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
|
||||
File.Copy(sourcePath, targetPath, true);
|
||||
}
|
||||
|
||||
Console.WriteLine($" {counter:###,###} web assets updated successfully.");
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Global using directives
|
||||
|
||||
global using System.Text;
|
||||
|
||||
global using Cocona;
|
||||
|
||||
global using Environment = Build.Tools.Environment;
|
||||
@@ -0,0 +1,9 @@
|
||||
using Build.Commands;
|
||||
|
||||
var builder = CoconaApp.CreateBuilder();
|
||||
var app = builder.Build();
|
||||
app.AddCommands<CheckRidsCommand>();
|
||||
app.AddCommands<UpdateMetadataCommands>();
|
||||
app.AddCommands<UpdateWebAssetsCommand>();
|
||||
app.AddCommands<CollectI18NKeysCommand>();
|
||||
app.Run();
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Build.Tools;
|
||||
|
||||
public static class Environment
|
||||
{
|
||||
public const string DOTNET_VERSION = "net9.0";
|
||||
public static readonly Encoding UTF8_NO_BOM = new UTF8Encoding(false);
|
||||
|
||||
private static readonly Dictionary<RID, string> ALL_RIDS = Enum.GetValues<RID>().Select(rid => new KeyValuePair<RID, string>(rid, rid.ToName())).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
|
||||
public static bool IsWorkingDirectoryValid()
|
||||
{
|
||||
var currentDirectory = Directory.GetCurrentDirectory();
|
||||
var mainFile = Path.Combine(currentDirectory, "Program.cs");
|
||||
var projectFile = Path.Combine(currentDirectory, "Build Script.csproj");
|
||||
|
||||
if (!currentDirectory.EndsWith("Build", StringComparison.Ordinal) || !File.Exists(mainFile) || !File.Exists(projectFile))
|
||||
{
|
||||
Console.WriteLine("The current directory is not a valid working directory for the build script. Go to the /app/Build directory within the git repository.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetAIStudioDirectory()
|
||||
{
|
||||
var currentDirectory = Directory.GetCurrentDirectory();
|
||||
var directory = Path.Combine(currentDirectory, "..", "MindWork AI Studio");
|
||||
return Path.GetFullPath(directory);
|
||||
}
|
||||
|
||||
public static string GetRustRuntimeDirectory()
|
||||
{
|
||||
var currentDirectory = Directory.GetCurrentDirectory();
|
||||
var directory = Path.Combine(currentDirectory, "..", "..", "runtime");
|
||||
return Path.GetFullPath(directory);
|
||||
}
|
||||
|
||||
public static string GetMetadataPath()
|
||||
{
|
||||
var currentDirectory = Directory.GetCurrentDirectory();
|
||||
var directory = Path.Combine(currentDirectory, "..", "..", "metadata.txt");
|
||||
return Path.GetFullPath(directory);
|
||||
}
|
||||
|
||||
public static string? GetOS()
|
||||
{
|
||||
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return "windows";
|
||||
|
||||
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return "linux";
|
||||
|
||||
if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
return "darwin";
|
||||
|
||||
Console.WriteLine($"Error: Unsupported OS '{RuntimeInformation.OSDescription}'");
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IEnumerable<RID> GetRidsForCurrentOS()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return ALL_RIDS.Where(rid => rid.Value.StartsWith("win-", StringComparison.Ordinal)).Select(n => n.Key);
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
return ALL_RIDS.Where(rid => rid.Value.StartsWith("osx-", StringComparison.Ordinal)).Select(n => n.Key);
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return ALL_RIDS.Where(rid => rid.Value.StartsWith("linux-", StringComparison.Ordinal)).Select(n => n.Key);
|
||||
|
||||
Console.WriteLine($"Error: Unsupported OS '{RuntimeInformation.OSDescription}'");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Build.Tools;
|
||||
|
||||
public enum RID
|
||||
{
|
||||
NONE,
|
||||
|
||||
WIN_X64,
|
||||
WIN_ARM64,
|
||||
|
||||
LINUX_X64,
|
||||
LINUX_ARM64,
|
||||
|
||||
OSX_X64,
|
||||
OSX_ARM64,
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Build.Tools;
|
||||
|
||||
public static class RIDExtensions
|
||||
{
|
||||
public static string ToName(this RID rid) => rid switch
|
||||
{
|
||||
RID.WIN_X64 => "win-x64",
|
||||
RID.WIN_ARM64 => "win-arm64",
|
||||
|
||||
RID.LINUX_X64 => "linux-x64",
|
||||
RID.LINUX_ARM64 => "linux-arm64",
|
||||
|
||||
RID.OSX_X64 => "osx-x64",
|
||||
RID.OSX_ARM64 => "osx-arm64",
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user