Skip to content

Commit cb6ff9c

Browse files
authored
Merge pull request #69 from guitarrapc/guitarrapc-patch-1
[BREAKING CHANGE] feat: Rename Settings to DefaultSettings, error handling.
2 parents a2b6c7b + ad9a184 commit cb6ff9c

7 files changed

Lines changed: 211 additions & 102 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace UnityBuildRunner.Core;
5+
6+
internal enum BuildErrorCode
7+
{
8+
[ErrorExitCode(0)]
9+
Success,
10+
[ErrorExitCode(9901)]
11+
ProcessNull,
12+
[ErrorExitCode(9902)]
13+
ProcessImmediatelyExit,
14+
[ErrorExitCode(9903)]
15+
ProcessTimeout,
16+
}
17+
18+
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
19+
internal sealed class ErrorExitCodeAttribute : Attribute
20+
{
21+
public int ExitCode { get; }
22+
public ErrorExitCodeAttribute(int exitCode) => ExitCode = exitCode;
23+
}
24+
25+
internal static class EnumExtensions
26+
{
27+
/// <summary>
28+
/// Obtain T Attribute from Enum
29+
/// </summary>
30+
/// <typeparam name="T"></typeparam>
31+
/// <param name="e"></param>
32+
/// <returns></returns>
33+
public static T? GetAttrubute<T>(this Enum e) where T : Attribute
34+
{
35+
var field = e.GetType().GetField(e.ToString());
36+
if (field is not null && field.GetCustomAttribute<T>() is T att)
37+
{
38+
return att;
39+
}
40+
return null;
41+
}
42+
}

src/UnityBuildRunner.Core/Builder.cs

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,14 @@ public DefaultBuilder(ILogger logger, IErrorFilter errorFilter)
4040

4141
public async Task<int> BuildAsync(ISettings settings, TimeSpan timeout)
4242
{
43-
logger.LogInformation($"Preparing Unity Build.");
44-
45-
// validate
46-
if (string.IsNullOrWhiteSpace(settings.UnityPath))
47-
throw new ArgumentException($"Please pass Unity Executable path with argument `--unity-path` or environment variable `{nameof(settings.UnityPath)}`.");
48-
if (!File.Exists(settings.UnityPath))
49-
throw new FileNotFoundException($"{nameof(settings.UnityPath)} not found.{settings.UnityPath}");
50-
if (string.IsNullOrEmpty(settings.LogFilePath))
51-
throw new ArgumentException("Missing '-logFile filename' argument. Make sure you had targeted any log file path.");
52-
5343
// Initialize
5444
logger.LogInformation($"Initializing LogFilePath '{settings.LogFilePath}'.");
5545
await InitializeAsync(settings.LogFilePath);
5646

5747
// Build
5848
logger.LogInformation("Starting Unity Build.");
59-
logger.LogInformation($"Command: {settings.UnityPath} {settings.ArgumentString}");
60-
logger.LogInformation($"WorkingDir: {settings.WorkingDirectory}");
49+
logger.LogInformation($" - Command: {settings.UnityPath} {settings.ArgumentString}");
50+
logger.LogInformation($" - WorkingDir: {settings.WorkingDirectory}");
6151
var sw = Stopwatch.StartNew();
6252
using var process = Process.Start(new ProcessStartInfo()
6353
{
@@ -68,9 +58,12 @@ public async Task<int> BuildAsync(ISettings settings, TimeSpan timeout)
6858
CreateNoWindow = true,
6959
});
7060

61+
var exitCode = BuildErrorCode.Success;
62+
7163
if (process is null)
7264
{
7365
sw.Stop();
66+
exitCode = BuildErrorCode.ProcessNull;
7467
throw new OperationCanceledException("Could not start Unity. Somthing blocked creating process.");
7568
}
7669

@@ -86,13 +79,15 @@ public async Task<int> BuildAsync(ISettings settings, TimeSpan timeout)
8679
}
8780
else
8881
{
82+
exitCode = BuildErrorCode.ProcessTimeout;
8983
throw new TimeoutException($"Unity Process has been aborted. Waited 10 seconds but could't create logFilePath '{settings.LogFilePath}'.");
9084
}
9185
}
9286

9387
// log file generated but process immediately exited.
9488
if (process.HasExited)
9589
{
90+
exitCode = BuildErrorCode.ProcessImmediatelyExit;
9691
throw new OperationCanceledException($"Unity process started but build unexpectedly finished before began.");
9792
}
9893

@@ -104,6 +99,7 @@ public async Task<int> BuildAsync(ISettings settings, TimeSpan timeout)
10499
{
105100
if (sw.Elapsed.TotalMilliseconds > timeout.TotalMilliseconds)
106101
{
102+
exitCode = BuildErrorCode.ProcessTimeout;
107103
throw new TimeoutException($"Timeout exceeded. {timeout.TotalMinutes}min has been passed, stopping build.");
108104
}
109105

@@ -124,46 +120,55 @@ public async Task<int> BuildAsync(ISettings settings, TimeSpan timeout)
124120
{
125121
sw.Stop();
126122

127-
if (process.ExitCode == 0)
123+
if (exitCode is BuildErrorCode.Success)
128124
{
129-
logger.LogInformation($"Unity Build successfully complete. ({process.ExitCode})");
125+
logger.LogInformation($"Unity Build successfully complete.");
130126
}
131127
else
132128
{
133-
logger.LogInformation($"Unity Build failed. ({process.ExitCode})");
129+
logger.LogInformation($"Unity Build failed.");
134130
}
135131

136132
logger.LogInformation($"Elapsed Time {sw.Elapsed}");
137133

138134
// Assume exit Unity process
139-
if (!process.HasExited)
135+
if (process is not null && !process.HasExited)
140136
{
141137
logger.LogInformation($"Killing unterminated process. ({process.Id})");
142138
process.Kill(true);
143139
}
144140
}
145-
return process.ExitCode;
141+
142+
return exitCode.GetAttrubute<ErrorExitCodeAttribute>()?.ExitCode ?? 0;
146143
}
147144

148-
public async Task InitializeAsync(string path)
145+
public async Task InitializeAsync(string logFilePath)
149146
{
150-
if (!File.Exists(path))
147+
await AssumeLogFileInitialized(logFilePath).ConfigureAwait(false);
148+
}
149+
150+
/// <summary>
151+
/// Assume Logfile is not exists, or delete before run.
152+
/// </summary>
153+
/// <param name="logFilePath"></param>
154+
/// <returns></returns>
155+
public async Task AssumeLogFileInitialized(string logFilePath)
156+
{
157+
if (!File.Exists(logFilePath))
151158
{
152159
return;
153160
}
154-
155-
// retry 10 times
156-
var retry = 10;
161+
var retry = 10; // retry 10 times
157162
for (var i = 1; i <= retry; i++)
158163
{
159164
try
160165
{
161-
File.Delete(path);
166+
File.Delete(logFilePath);
162167
break;
163168
}
164169
catch (IOException) when (i < retry + 1)
165170
{
166-
logger.LogWarning($"Couldn't delete file {path}, retrying... ({i + 1}/{retry})");
171+
logger.LogWarning($"Couldn't delete file {logFilePath}, retrying... ({i + 1}/{retry})");
167172
await Task.Delay(TimeSpan.FromSeconds(1));
168173
continue;
169174
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.IO;
4+
using System.Linq;
5+
6+
namespace UnityBuildRunner.Core;
7+
8+
public interface ISettings
9+
{
10+
string[] Args { get; }
11+
string ArgumentString { get; }
12+
string UnityPath { get; }
13+
string LogFilePath { get; }
14+
string WorkingDirectory { get; }
15+
}
16+
17+
public record DefaultSettings(string[] Args, string ArgumentString, string UnityPath, string LogFilePath, string WorkingDirectory) : ISettings
18+
{
19+
/// <summary>
20+
/// Validate Settings is correct.
21+
/// </summary>
22+
public void Validate()
23+
{
24+
// validate
25+
if (string.IsNullOrWhiteSpace(UnityPath))
26+
throw new ArgumentException($"Please pass Unity Executable path with argument '--unity-path' or environment variable '{nameof(UnityPath)}'.");
27+
if (string.IsNullOrEmpty(LogFilePath))
28+
throw new ArgumentException("Missing '-logFile filename' argument. Make sure you had targeted any log file path.");
29+
if (!File.Exists(UnityPath))
30+
throw new FileNotFoundException($"{nameof(UnityPath)} not found. {UnityPath}");
31+
}
32+
33+
/// <summary>
34+
/// Parse args and generate Settings.
35+
/// </summary>
36+
public static bool TryParse(string[] args, string unityPath, [NotNullWhen(true)] out DefaultSettings? settings)
37+
{
38+
try
39+
{
40+
settings = Parse(args, unityPath);
41+
return true;
42+
}
43+
catch (Exception)
44+
{
45+
settings = null;
46+
return false;
47+
}
48+
}
49+
50+
/// <summary>
51+
/// Parse args and generate Settings.
52+
/// </summary>
53+
public static DefaultSettings Parse(string[] args, string unityPath)
54+
{
55+
// Unity Path
56+
var unityPathFixed = !string.IsNullOrWhiteSpace(unityPath) ? unityPath : Environment.GetEnvironmentVariable(nameof(UnityPath)) ?? throw new ArgumentNullException("Unity Path not specified. Please specify via argument or Environment Variable.");
57+
58+
var logFilePath = GetLogFile(args);
59+
// fallback logfilePath
60+
if (string.IsNullOrWhiteSpace(logFilePath))
61+
{
62+
logFilePath = "unitybuild.log";
63+
args = args.Concat(new[] { "-logFile", logFilePath }).ToArray();
64+
}
65+
66+
var arguments = args.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
67+
var argumentString = string.Join(" ", arguments.Select(s => s.First() == '-' ? s : "\"" + s + "\""));
68+
69+
// WorkingDirectory should be cli launch path.
70+
var workingDirectory = Directory.GetCurrentDirectory();
71+
72+
// Create settings and validate
73+
var settings = new DefaultSettings(arguments, argumentString, unityPathFixed, logFilePath, workingDirectory);
74+
settings.Validate();
75+
76+
return settings;
77+
}
78+
79+
internal static string GetLogFile(string[] args)
80+
{
81+
var logFile = "";
82+
for (var i = 0; i < args.Length; i++)
83+
{
84+
if (string.Equals(args[i], "-logFile", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
85+
{
86+
logFile = args[i + 1];
87+
break;
88+
}
89+
}
90+
return logFile;
91+
}
92+
}

src/UnityBuildRunner.Core/Settings.cs

Lines changed: 0 additions & 53 deletions
This file was deleted.

src/UnityBuildRunner.Core/UnityBuildRunner.Core.csproj

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<TargetFrameworks>netcoreapp6.0</TargetFrameworks>
5-
<LangVersion>latest</LangVersion>
5+
<LangVersion>latest</LangVersion>
66
<Nullable>enable</Nullable>
77

88
<Version>1.0.0</Version>
@@ -20,9 +20,15 @@
2020
</PropertyGroup>
2121

2222
<ItemGroup>
23-
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
24-
</ItemGroup>
25-
23+
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
28+
<_Parameter1>UnityBuildRunner.Core.Tests</_Parameter1>
29+
</AssemblyAttribute>
30+
</ItemGroup>
31+
2632
<ItemGroup>
2733
<None Include="..\..\LICENSE.md">
2834
<Pack>True</Pack>

src/UnityBuildRunner/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public async Task<int> Run([Option("--unity-path", "Full Path to the Unity.exe (
3333
if (arguments is not null && arguments.Any())
3434
{
3535
var builder = new DefaultBuilder(logger);
36-
var settings = Settings.Parse(arguments.ToArray()!, unityPath);
36+
var settings = DefaultSettings.Parse(arguments.ToArray()!, unityPath);
3737
var timeoutSpan = TimeSpan.TryParse(timeout, out var r) ? r : TimeSpan.FromMinutes(60);
3838

3939
return await builder.BuildAsync(settings, timeoutSpan);

0 commit comments

Comments
 (0)