Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Extract strings #528

Merged
merged 21 commits into from
Aug 5, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Update installer
  • Loading branch information
sailro committed Aug 5, 2024

Verified

This commit was signed with the committer’s verified signature.
snyk-bot Snyk bot
commit 9003cefd271ca185791f98f8c6d53e70104184dc
2 changes: 1 addition & 1 deletion Features/FeatureRenderer.cs
Original file line number Diff line number Diff line change
@@ -157,7 +157,7 @@ private static string RenderFeatureText(Feature feature)
if (feature is not ToggleFeature toggleFeature || ConfigurationManager.IsSkippedProperty(feature, nameof(Enabled)))
return feature.Name;

return string.Format(Strings.CommandStatusTextFormat, toggleFeature.Enabled ? Strings.TextOn.Green() : Strings.TextOff.Red());
return string.Format(Strings.CommandStatusTextFormat, feature.Name, toggleFeature.Enabled ? Strings.TextOn.Green() : Strings.TextOff.Red(), string.Empty);
}

private void RenderSummary()
13 changes: 13 additions & 0 deletions Installer/CompilationResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.IO.Compression;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Installer;

internal class CompilationResult(CSharpCompilation? compilation, ZipArchive? archive, Diagnostic[] errors, ResourceDescription[] resources)
{
public CSharpCompilation? Compilation { get; } = compilation;
public ZipArchive? Archive { get; } = archive;
public Diagnostic[] Errors { get; } = errors;
public ResourceDescription[] Resources { get; } = resources;
}
44 changes: 44 additions & 0 deletions Installer/Compiler.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Resources;
using System.Resources.NetStandard;
using System.Text;
using System.Text.RegularExpressions;
using Installer.Properties;
@@ -156,6 +159,44 @@ private IEnumerable<SyntaxTree> GetSyntaxTrees()
}
}

public IEnumerable<ResourceDescription> GetResources()
{
var matches = ResourceFileRegex().Matches(ProjectContent);

foreach (var match in matches.Cast<Match>())
{
if (!match.Success)
continue;

var file = match.Groups["file"].Value;
var entry = ProjectArchive.Entries.FirstOrDefault(e => e.FullName.EndsWith(file.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase));
if (entry == null)
continue;

using var stream = entry.Open();
using var reader = new ResXResourceReader(stream);

using var memory = new MemoryStream();
using var writer = new ResourceWriter(memory);

foreach (DictionaryEntry resourcEntry in reader)
writer.AddResource(resourcEntry.Key.ToString()!, resourcEntry.Value);

var resource = new MemoryStream();

writer.Generate();
memory.Position = 0;
memory.CopyTo(resource);
resource.Position = 0;

var resourceName = file
.Replace(@"Properties\Strings", "EFT.Trainer.Properties.Strings")
.Replace(".resx", ".resources");

yield return new ResourceDescription(resourceName, () => resource, isPublic: true);
}
}

public CSharpCompilation Compile(string assemblyName)
{
var syntaxTrees = GetSyntaxTrees()
@@ -172,4 +213,7 @@ public CSharpCompilation Compile(string assemblyName)

[GeneratedRegex("<(Project)?Reference\\s+Include=\"(?<assemblyName>.*)\"\\s*/?>")]
private static partial Regex ProjectReferenceRegex();

[GeneratedRegex("<EmbeddedResource\\s+Include=\"(?<file>.*)\"\\s*/?>")]
private static partial Regex ResourceFileRegex();
}
79 changes: 55 additions & 24 deletions Installer/InstallCommand.cs
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
using Installer.Properties;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using Spectre.Console;
using Spectre.Console.Cli;

@@ -67,9 +68,9 @@ public override async Task<int> ExecuteAsync(CommandContext commandContext, Sett
settings.DisabledFeatures = ToSourceFile(settings.DisabledFeatures, features);
settings.DisabledCommands = ToSourceFile(settings.DisabledCommands, commands);

var (compilation, archive) = await BuildTrainerAsync(settings, installation, features, commands);
var result = await BuildTrainerAsync(settings, installation, features, commands);

if (compilation == null)
if (result.Compilation == null)
{
// Failure
AnsiConsole.MarkupLine($"[red]Unable to compile trainer for version {installation.Version}. Please file an issue here : https://github.com/sailro/EscapeFromTarkov-Trainer/issues [/]");
@@ -86,35 +87,35 @@ public override async Task<int> ExecuteAsync(CommandContext commandContext, Sett
return (int)ExitCode.Canceled;
}

if (!CreateDll(installation, "NLog.EFT.Trainer.dll", dllPath => compilation.Emit(dllPath)))
if (!CreateDll(installation, "NLog.EFT.Trainer.dll", dllPath => result.Compilation.Emit(dllPath, manifestResources: result.Resources)))
return (int)ExitCode.CreateDllFailed;

if (!CreateDll(installation, "0Harmony.dll", dllPath => File.WriteAllBytes(dllPath, Resources._0Harmony), false))
return (int)ExitCode.CreateHarmonyDllFailed;

if (!CreateOutline(installation, archive!))
if (!CreateOutline(installation, result.Archive!))
return (int)ExitCode.CreateOutlineFailed;

const string bepInExPluginProject = "BepInExPlugin.csproj";
if (installation.UsingBepInEx && archive!.Entries.Any(e => e.Name == bepInExPluginProject))
if (installation.UsingBepInEx && result.Archive!.Entries.Any(e => e.Name == bepInExPluginProject))
{
AnsiConsole.MarkupLine("[green][[BepInEx]][/] detected. Creating plugin instead of using NLog configuration.");

// reuse successful context for compiling.
var pluginContext = new CompilationContext(installation, "plugin", bepInExPluginProject)
{
Archive = archive,
Archive = result.Archive,
Branch = GetInitialBranch(settings)
};
var (pluginCompilation, _, _) = await GetCompilationAsync(pluginContext);
var pluginResult = await GetCompilationAsync(pluginContext);

if (pluginCompilation == null)
if (pluginResult.Compilation == null)
{
AnsiConsole.MarkupLine($"[red]Unable to compile plugin for version {installation.Version}. Please file an issue here : https://github.com/sailro/EscapeFromTarkov-Trainer/issues [/]");
return (int)ExitCode.PluginCompilationFailed;
}

if (!CreateDll(installation, Path.Combine(installation.BepInExPlugins, "aki-efttrainer.dll"), dllPath => pluginCompilation.Emit(dllPath)))
if (!CreateDll(installation, Path.Combine(installation.BepInExPlugins, "aki-efttrainer.dll"), dllPath => pluginResult.Compilation.Emit(dllPath)))
return (int)ExitCode.CreatePluginDllFailed;
}
else
@@ -140,7 +141,7 @@ public override async Task<int> ExecuteAsync(CommandContext commandContext, Sett
return (int)ExitCode.Success;
}

private static async Task<(CSharpCompilation?, ZipArchive?)> BuildTrainerAsync(Settings settings, Installation installation, params string[] folders)
private static async Task<CompilationResult> BuildTrainerAsync(Settings settings, Installation installation, params string[] folders)
{
// Try first to compile against master
var context = new CompilationContext(installation, "trainer", "NLog.EFT.Trainer.csproj")
@@ -149,42 +150,42 @@ public override async Task<int> ExecuteAsync(CommandContext commandContext, Sett
Branch = GetInitialBranch(settings)
};

var (compilation, archive, errors) = await GetCompilationAsync(context);
var files = errors
var result = await GetCompilationAsync(context);
var files = result.Errors
.Select(d => d.Location.SourceTree?.FilePath)
.Where(s => s is not null)
.Distinct()
.ToArray();

if (context.IsFatalFailure)
return (compilation, archive);
return result;

if (compilation == null)
if (result.Compilation == null)
{
// Failure, so try with a dedicated branch if exists
var retryBranch = GetRetryBranch(installation, context);
if (retryBranch != null)
{
context.Branch = retryBranch;
(compilation, archive, _) = await GetCompilationAsync(context);
result = await GetCompilationAsync(context);
}
}

if (compilation == null && files.Length != 0 && files.All(file => folders.Any(folder => file!.StartsWith(folder))))
if (result.Compilation == null && files.Length != 0 && files.All(file => folders.Any(folder => file!.StartsWith(folder))))
{
// Failure, retry by removing faulting features if possible
AnsiConsole.MarkupLine($"[yellow]Trying to disable faulting feature/command: [red]{GetFaultingNames(files!)}[/].[/]");

context.Exclude = [.. files!, .. settings.DisabledFeatures!, .. settings.DisabledCommands!];
context.Branch = GetFallbackBranch(settings);

(compilation, archive, errors) = await GetCompilationAsync(context);
result = await GetCompilationAsync(context);

if (errors.Length == 0)
if (result.Errors.Length == 0)
AnsiConsole.MarkupLine("[yellow]We found a fallback! But please file an issue here : https://github.com/sailro/EscapeFromTarkov-Trainer/issues [/]");
}

return (compilation, archive);
return result;
}

private static string GetFaultingNames(string[] files)
@@ -234,18 +235,20 @@ private static void TryCreateGameDocumentFolder()
}
}

private static async Task<(CSharpCompilation?, ZipArchive?, Diagnostic[])> GetCompilationAsync(CompilationContext context)
private static async Task<CompilationResult> GetCompilationAsync(CompilationContext context)
{
var errors = Array.Empty<Diagnostic>();
ResourceDescription[] resources = [];

var archive = context.Archive ?? await GetSnapshotAsync(context, context.Branch);
if (archive == null)
{
context.Try++;
return (null, null, errors);
return new(null, null, errors, resources);
}

CSharpCompilation? compilation = null;

AnsiConsole
.Status()
.Start($"Compiling {context.ProjectTitle}", _ =>
@@ -269,12 +272,16 @@ private static void TryCreateGameDocumentFolder()
}
else
{
resources = compiler
.GetResources()
.ToArray();

AnsiConsole.MarkupLine($">> [blue]Try #{context.Try}[/] Compilation [green]succeed[/] for [blue]{context.Branch.EscapeMarkup()}[/] branch.");
}
});

context.Try++;
return (compilation, archive, errors);
return new(compilation, archive, errors, resources);
}

private static async Task<ZipArchive?> GetSnapshotAsync(CompilationContext context, string branch)
@@ -418,6 +425,15 @@ private static bool CreateOutline(Installation installation, ZipArchive archive)
}

private static bool CreateDll(Installation installation, string filename, Action<string> creator, bool overwrite = true)
{
return CreateDll(installation, filename, s =>
{
creator(s);
return null;
}, overwrite);
}

private static bool CreateDll(Installation installation, string filename, Func<string, EmitResult?> creator, bool overwrite = true)
{
var dllPath = Path.IsPathRooted(filename) ? filename : Path.Combine(installation.Managed, filename);
var dllPathBepInExCore = Path.IsPathRooted(filename) ? null : Path.Combine(installation.BepInExCore, filename);
@@ -431,9 +447,24 @@ private static bool CreateDll(Installation installation, string filename, Action
if (!overwrite && File.Exists(dllPath))
return true;

creator(dllPath);
AnsiConsole.MarkupLine($"Created [green]{Path.GetFileName(dllPath).EscapeMarkup()}[/] in [blue]{Path.GetDirectoryName(dllPath).EscapeMarkup()}[/].");
var result = creator(dllPath);
if (result != null)
{
var errors = result
.Diagnostics
.Where(d => d.Severity == DiagnosticSeverity.Error)
.ToArray();

#if DEBUG
foreach (var error in errors)
AnsiConsole.MarkupLine($"[grey]>> {error.Id} [[{error.Location.SourceTree?.FilePath.EscapeMarkup()}]]: {error.GetMessage().EscapeMarkup()}.[/]");
#endif

if (!result.Success)
throw new Exception(errors.FirstOrDefault()?.GetMessage() ?? "Unknown error while emitting assembly");
}

AnsiConsole.MarkupLine($"Created [green]{Path.GetFileName(dllPath).EscapeMarkup()}[/] in [blue]{Path.GetDirectoryName(dllPath).EscapeMarkup()}[/].");
return true;
}
catch (Exception ex)
2 changes: 2 additions & 0 deletions Installer/Installer.csproj
Original file line number Diff line number Diff line change
@@ -27,13 +27,15 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
<PackageReference Include="Microsoft.Unity.Analyzers" Version="1.19.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48" PrivateAssets="all" />
<PackageReference Include="ResXResourceReader.NetStandard" Version="1.3.0" />
<PackageReference Include="Spectre.Console.Cli" Version="0.49.1" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
<PackageReference Include="System.Memory" Version="4.5.5" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="System.Reflection.Metadata" Version="8.0.0" />
<PackageReference Include="System.Resources.Writer" Version="4.3.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
2 changes: 1 addition & 1 deletion Properties/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Properties/Strings.resx
Original file line number Diff line number Diff line change
@@ -791,7 +791,7 @@
<comment>Item count</comment>
</data>
<data name="DebugRegisteringCommandFormat" xml:space="preserve">
<value>Registering {0} command ...</value>
<value>Registering {0} command...</value>
<comment>Command name</comment>
</data>
<data name="DebugRegisteringCommandWithArgumentsFormat" xml:space="preserve">