Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 13 additions & 3 deletions .github/workflows/unity-xref-maps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,25 @@ jobs:
shell: pwsh
id: set-unity-releases-versions
run: |
$url = 'https://services.api.unity.com/unity/editor/release/v1/releases?' + `
$baseUrl = 'https://services.api.unity.com/unity/editor/release/v1/releases?' + `
'architecture=X86_64&' + `
'platform=WINDOWS&' + `
'limit=25&' + `
'stream=LTS&' + `
'stream=SUPPORTED&' + `
'stream=TECH'

$response = Invoke-RestMethod $url
$allResults = @()
$offset = 0

$versions = $response.results
do
{
$response = Invoke-RestMethod "$baseUrl&offset=$offset"
$allResults += $response.results
$offset += 25
} while ($response.results.Count -gt 0)

$versions = $allResults
| Select-Object -ExpandProperty version
| ForEach-Object {
$match = [regex]::Match($_, '(?<Version>(?<ShortVersion>(\d+)\.(\d+))\.(\d+))')
Expand Down Expand Up @@ -214,6 +222,8 @@ jobs:
--docFxConfigurationFilePath '${{ runner.temp }}/Work/docfx.json' `
--xrefMapsPath "${{ runner.temp }}/_site/$($version.ShortVersion)/xrefmap.yml"

if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Upload artifact
uses: actions/upload-artifact@v5
with:
Expand Down
24 changes: 24 additions & 0 deletions UnityXrefMaps.Tests/UtilsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Threading.Tasks;

namespace UnityXrefMaps.Tests;

public class UtilsTests
{
[Fact]
public async Task RunCommand_SuccessfulProcess_ReturnsZero()
{
// `true` always exits with code 0.
// RunCommand must surface the exit code so callers can detect failures.
int exitCode = await Utils.RunCommand("true", "", _ => { }, _ => { });
Assert.Equal(0, exitCode);
}

[Fact]
public async Task RunCommand_FailingProcess_ReturnsNonZero()
{
// `false` always exits with code 1.
// Before the fix, RunCommand returned void so exit codes were silently discarded.
int exitCode = await Utils.RunCommand("false", "", _ => { }, _ => { });
Assert.Equal(1, exitCode);
}
}
68 changes: 68 additions & 0 deletions UnityXrefMaps.Tests/XrefMapServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;

namespace UnityXrefMaps.Tests;

public class XrefMapServiceTests
{
private readonly XrefMapService _service = new(NullLogger<XrefMapService>.Instance);

[Fact]
public async Task Load_ZeroColonAtWordBoundary_IsStripped()
{
// `0: value` in a YAML string value causes the parser to treat it as a nested
// mapping, crashing with a type-mismatch. The regex must strip `0:` before parsing.
string yaml = """
sorted: false
references:
- uid: SomeType
name: SomeType
href: SomeType.html
commentId: T:SomeType
nameWithType: 0: orphan
""";

string filePath = Path.GetTempFileName();
try
{
await File.WriteAllTextAsync(filePath, yaml);
XrefMap result = await _service.Load(filePath);
Assert.NotNull(result);
Assert.Single(result.References!);
}
finally
{
File.Delete(filePath);
}
}

[Fact]
public async Task Load_NonZeroDigitColon_IsPreserved()
{
// The old regex `(\d):` stripped the colon from every digit-colon sequence, corrupting
// legitimate values such as `data1:field` → `data1field` in href or nameWithType fields.
// Only `0:` at a word boundary should be stripped.
string yaml = """
sorted: false
references:
- uid: SomeType
name: SomeType
href: SomeType.html
commentId: T:SomeType
nameWithType: data1:field
""";

string filePath = Path.GetTempFileName();
try
{
await File.WriteAllTextAsync(filePath, yaml);
XrefMap result = await _service.Load(filePath);
Assert.Equal("data1:field", result.References![0].NameWithType);
}
finally
{
File.Delete(filePath);
}
}
}
26 changes: 19 additions & 7 deletions UnityXrefMaps/Commands/BuildCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ public BuildCommand(ILoggerFactory loggerFactory)
string docFxFileDirectoryPath = Path.GetDirectoryName(docFxFilePath)!;

string generatedDocsPath = Path.Combine(docFxFileDirectoryPath, docFxConfiguration!.Build!.Destination!);
string generatedXrefMapPath = Path.Combine(generatedDocsPath, Constants.DefaultXrefMapFileName!);
string generatedXrefMapPath = Path.Combine(generatedDocsPath, Constants.DefaultXrefMapFileName!);

ILogger logger = loggerFactory.CreateLogger<BuildCommand>();

if (logger.IsEnabled(LogLevel.Information))
Expand All @@ -124,9 +124,9 @@ public BuildCommand(ILoggerFactory loggerFactory)
docFxArguments += ' ' + docFxAdditionalArguments;
}

if (repositoryTags == null || repositoryTags.Length == 0)
if (repositoryTags == null || repositoryTags.Length == 0)
{
repositoryTags = [.. repository.GetLatestVersions().Select(v => v.release)];
repositoryTags = [.. repository.GetLatestVersions().Select(v => v.release)];
}

foreach (string repositoryTag in repositoryTags)
Expand All @@ -149,7 +149,7 @@ public BuildCommand(ILoggerFactory loggerFactory)
logger.LogInformation("Running DocFX on '{RepositoryTag}'", repositoryTag);
}

await Utils.RunCommand(
int exitCode = await Utils.RunCommand(
"docfx", docFxArguments,
value =>
{
Expand All @@ -173,6 +173,18 @@ await Utils.RunCommand(
},
cancellationToken);

if (exitCode != 0)
{
result = false;

if (logger.IsEnabled(LogLevel.Error))
{
logger.LogError("DocFX exited with code {ExitCode} for Unity '{RepositoryTag}'", exitCode, repositoryTag);
}

continue;
}

if (!File.Exists(generatedXrefMapPath))
{
result = false;
Expand All @@ -192,8 +204,8 @@ await Utils.RunCommand(

await Utils.CopyFile(generatedXrefMapPath, xrefMapPath, cancellationToken);

XrefMap xrefMap = await xrefMapService.Load(xrefMapPath, cancellationToken);
XrefMap xrefMap = await xrefMapService.Load(xrefMapPath, cancellationToken);

xrefMap.References = [.. xrefMapService.Process(string.Format(apiUrl!, shortVersion), xrefMap.References!, trimNamespaces!, isPackage)];

await xrefMapService.Save(xrefMapPath, xrefMap, cancellationToken);
Expand Down
4 changes: 3 additions & 1 deletion UnityXrefMaps/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static async Task CopyFile(string sourcePath, string destPath, Cancellati
/// <param name="arguments">The arguments of the command.</param>
/// <param name="output">The function to call with the output data of the command.</param>
/// <param name="error">The function to call with the error data of the command.</param>
public static async Task RunCommand(string command, string arguments, Action<string?> output, Action<string?> error, CancellationToken cancellationToken = default)
public static async Task<int> RunCommand(string command, string arguments, Action<string?> output, Action<string?> error, CancellationToken cancellationToken = default)
{
using var process = new Process();
process.StartInfo = new ProcessStartInfo(command, arguments)
Expand All @@ -58,6 +58,8 @@ public static async Task RunCommand(string command, string arguments, Action<str
process.BeginErrorReadLine();

await process.WaitForExitAsync(cancellationToken);

return process.ExitCode;
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions UnityXrefMaps/XrefMapService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async Task<XrefMap> Load(string filePath, CancellationToken cancellationT
string xrefMapText = await File.ReadAllTextAsync(filePath, cancellationToken);

// Remove `0:` strings on the xrefmap that make crash Deserializer
xrefMapText = ZeroStringsRegex().Replace(xrefMapText, "$1");
xrefMapText = ZeroStringsRegex().Replace(xrefMapText, "0");

return _deserializer.Deserialize<XrefMap>(xrefMapText);
}
Expand Down Expand Up @@ -77,7 +77,7 @@ public async Task Save(string filePath, XrefMap xrefMap, CancellationToken cance
await File.WriteAllTextAsync(filePath, xrefMapText, cancellationToken);
}

[GeneratedRegex(@"(\d):")]
[GeneratedRegex(@"\b0:")]
private static partial Regex ZeroStringsRegex();
}
}