Add GenerateArrays task
This commit is contained in:
parent
33cd96ba0e
commit
a2032ade65
4 changed files with 278 additions and 171 deletions
168
.gitignore
vendored
168
.gitignore
vendored
|
@ -1,168 +0,0 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
.vs
|
||||
.vscode
|
||||
|
||||
# Build results
|
||||
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
build/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
|
||||
!packages/*/build/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.scc
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# Rider is a Visual Studio alternative
|
||||
.idea/*
|
||||
|
||||
# NCrunch
|
||||
*.ncrunch*
|
||||
.*crunch*.local.xml
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
|
||||
# NuGet Packages Directory
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
packages/
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
sql/
|
||||
*.Cache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.[Pp]ublish.xml
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
packages/*
|
||||
*.config
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
App_Data/*.mdf
|
||||
App_Data/*.ldf
|
||||
|
||||
|
||||
#LightSwitch generated files
|
||||
GeneratedArtifacts/
|
||||
_Pvt_Extensions/
|
||||
ModelManifest.xml
|
||||
|
||||
# =========================
|
||||
# Windows detritus
|
||||
# =========================
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Mac desktop service store files
|
||||
.DS_Store
|
||||
|
||||
# VS Launch Settings
|
||||
launchSettings.json
|
||||
|
||||
# NetCore Publishing Profiles
|
||||
PublishProfiles/
|
243
Ryujinx.CustomTasks/GenerateArrays.cs
Normal file
243
Ryujinx.CustomTasks/GenerateArrays.cs
Normal file
|
@ -0,0 +1,243 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.Build.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Ryujinx.CustomTasks.SyntaxWalker;
|
||||
using Ryujinx.CustomTasks.Helper;
|
||||
using Task = Microsoft.Build.Utilities.Task;
|
||||
|
||||
namespace Ryujinx.CustomTasks
|
||||
{
|
||||
public class GenerateArrays : Task
|
||||
{
|
||||
private const string InterfaceFileName = "IArray.g.cs";
|
||||
private const string ArraysFileName = "Arrays.g.cs";
|
||||
|
||||
private readonly List<string> _outputFiles = new List<string>();
|
||||
|
||||
[Required]
|
||||
public string ArrayNamespace { get; set; }
|
||||
|
||||
[Required]
|
||||
public string OutputPath { get; set; }
|
||||
|
||||
[Required]
|
||||
public ITaskItem[] InputFiles { get; set; }
|
||||
|
||||
[Output]
|
||||
public string[] OutputFiles { get; set; }
|
||||
|
||||
private static int GetAvailableMaxSize(IReadOnlyList<int> availableSizes, ref int index, int missingSize)
|
||||
{
|
||||
if (availableSizes.Count == 0 || missingSize == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (availableSizes.Count < index + 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int size = 0;
|
||||
|
||||
while (size == 0 || size > missingSize && availableSizes.Count - index > 0)
|
||||
{
|
||||
index++;
|
||||
size = availableSizes[availableSizes.Count - index];
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private void AddGeneratedSource(string filePath, string content)
|
||||
{
|
||||
File.WriteAllText(filePath, content);
|
||||
_outputFiles.Add(filePath);
|
||||
}
|
||||
|
||||
private ICollection<int> GetArraySizes(string itemPath)
|
||||
{
|
||||
Log.LogMessage(MessageImportance.Low, $"Searching for StructArray types in: {itemPath}");
|
||||
|
||||
SyntaxTree tree = CSharpSyntaxTree.ParseText(File.ReadAllText(itemPath), path: itemPath);
|
||||
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
|
||||
|
||||
ArraySizeCollector collector = new ArraySizeCollector();
|
||||
|
||||
collector.Visit(root);
|
||||
|
||||
foreach (int size in collector.ArraySizes)
|
||||
{
|
||||
Log.LogMessage(MessageImportance.Low, $"\tFound array size: {size}");
|
||||
}
|
||||
|
||||
return collector.ArraySizes;
|
||||
}
|
||||
|
||||
private string GenerateInterface()
|
||||
{
|
||||
CodeGenerator generator = new CodeGenerator();
|
||||
|
||||
generator.EnterScope($"namespace {ArrayNamespace}");
|
||||
|
||||
generator.AppendLine("/// <summary>");
|
||||
generator.AppendLine("/// Array interface.");
|
||||
generator.AppendLine("/// </summary>");
|
||||
generator.AppendLine("/// <typeparam name=\"T\">Element type</typeparam>);");
|
||||
generator.EnterScope("public interface IArray<T> where T : unmanaged");
|
||||
|
||||
generator.AppendLine("/// <summary>");
|
||||
generator.AppendLine("/// Used to index the array.");
|
||||
generator.AppendLine("/// </summary>");
|
||||
generator.AppendLine("/// <param name=\"index\">Element index</param>");
|
||||
generator.AppendLine("/// <returns>Element at the specified index</returns>");
|
||||
generator.AppendLine("ref T this[int index] { get; }");
|
||||
|
||||
generator.AppendLine();
|
||||
|
||||
generator.AppendLine("/// <summary>");
|
||||
generator.AppendLine("/// Number of elements on the array.");
|
||||
generator.AppendLine("/// </summary>");
|
||||
generator.AppendLine("int Length { get; }");
|
||||
|
||||
generator.LeaveScope();
|
||||
generator.LeaveScope();
|
||||
|
||||
return generator.ToString();
|
||||
}
|
||||
|
||||
private void GenerateArray(CodeGenerator generator, int size, IReadOnlyList<int> availableSizes)
|
||||
{
|
||||
generator.EnterScope($"public struct Array{size}<T> : IArray<T> where T : unmanaged");
|
||||
|
||||
generator.AppendLine("T _e0;");
|
||||
|
||||
if (size > 1)
|
||||
{
|
||||
generator.AppendLine("#pragma warning disable CS0169");
|
||||
}
|
||||
|
||||
if (availableSizes.Count == 0)
|
||||
{
|
||||
for (int i = 1; i < size; i++)
|
||||
{
|
||||
generator.AppendLine($"T _e{i};");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int counter = 1;
|
||||
int currentSize = 1;
|
||||
int maxSizeIndex = 0;
|
||||
int maxSize = 0;
|
||||
|
||||
while (currentSize < size)
|
||||
{
|
||||
if (maxSize == 0 || currentSize + maxSize > size)
|
||||
{
|
||||
maxSize = GetAvailableMaxSize(availableSizes, ref maxSizeIndex, size - currentSize);
|
||||
}
|
||||
|
||||
generator.AppendLine(maxSize > 1
|
||||
? counter == 1
|
||||
? $"Array{maxSize}<T> _other;"
|
||||
: $"Array{maxSize}<T> _other{counter};"
|
||||
: $"T _e{counter};"
|
||||
);
|
||||
|
||||
counter++;
|
||||
currentSize += maxSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (size > 1)
|
||||
{
|
||||
generator.AppendLine("#pragma warning restore CS0169");
|
||||
}
|
||||
|
||||
generator.AppendLine();
|
||||
generator.AppendLine($"public int Length => {size};");
|
||||
generator.AppendLine("public ref T this[int index] => ref AsSpan()[index];");
|
||||
generator.AppendLine("public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);");
|
||||
|
||||
generator.LeaveScope();
|
||||
generator.AppendLine();
|
||||
}
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
string interfaceFilePath = Path.Combine(OutputPath, InterfaceFileName);
|
||||
string arraysFilePath = Path.Combine(OutputPath, ArraysFileName);
|
||||
List<int> arraySizes = new List<int>();
|
||||
|
||||
File.Delete(interfaceFilePath);
|
||||
File.Delete(arraysFilePath);
|
||||
|
||||
foreach (var item in InputFiles)
|
||||
{
|
||||
string fullPath = item.GetMetadata("FullPath");
|
||||
|
||||
if (fullPath.EndsWith(".g.cs") || fullPath.Contains("obj\\Debug\\") || fullPath.Contains("obj\\Release\\"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (int size in GetArraySizes(fullPath))
|
||||
{
|
||||
if (!arraySizes.Contains(size))
|
||||
{
|
||||
arraySizes.Add(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (arraySizes.Count == 0)
|
||||
{
|
||||
Log.LogWarning("No StructArray types found. Skipping code generation.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
arraySizes.Sort();
|
||||
|
||||
AddGeneratedSource(interfaceFilePath, GenerateInterface());
|
||||
|
||||
CodeGenerator generator = new CodeGenerator();
|
||||
List<int> sizesAvailable = new List<int>();
|
||||
|
||||
generator.AppendLine("using System;");
|
||||
generator.AppendLine("using System.Runtime.InteropServices;");
|
||||
generator.AppendLine();
|
||||
|
||||
generator.EnterScope($"namespace {ArrayNamespace}");
|
||||
|
||||
// Always generate Arrays for 1, 2 and 3
|
||||
GenerateArray(generator, 1, sizesAvailable);
|
||||
sizesAvailable.Add(1);
|
||||
GenerateArray(generator, 2, sizesAvailable);
|
||||
sizesAvailable.Add(2);
|
||||
GenerateArray(generator, 3, sizesAvailable);
|
||||
sizesAvailable.Add(3);
|
||||
|
||||
foreach (var size in arraySizes)
|
||||
{
|
||||
if (!sizesAvailable.Contains(size))
|
||||
{
|
||||
GenerateArray(generator, size, sizesAvailable);
|
||||
sizesAvailable.Add(size);
|
||||
}
|
||||
}
|
||||
|
||||
generator.LeaveScope();
|
||||
|
||||
AddGeneratedSource(arraysFilePath, generator.ToString());
|
||||
|
||||
OutputFiles = _outputFiles.ToArray();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>8</LangVersion>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
<PackageId>Ryujinx.CustomTasks</PackageId>
|
||||
<Title>Ryujinx.CustomTasks</Title>
|
||||
<Description>A collection of custom MSBuild tasks.</Description>
|
||||
<Version>1.0.0</Version>
|
||||
<RepositoryUrl>https://github.com/Ryujinx/Ryujinx.CustomTasks</RepositoryUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
||||
|
@ -19,7 +20,6 @@
|
|||
<NoWarn>NU5100</NoWarn>
|
||||
<!-- Tell the SDK to generate a deps.json file -->
|
||||
<GenerateDependencyFile>true</GenerateDependencyFile>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -34,9 +34,10 @@
|
|||
|
||||
<!-- This is the target we defined above. It's purpose is to add all of our PackageReference and ProjectReference's runtime assets to our package output. -->
|
||||
<!-- Currently we have to manually specify the runtime assets, because for some reason ReferenceCopyLocalPaths is always empty. -->
|
||||
<!-- TODO: Copy dependencies to the lib directory instead of the tasks directory -->
|
||||
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences">
|
||||
<ItemGroup>
|
||||
<BuildOutputInPackage Include="bin\$(Configuration)\*\Microsoft.*.dll" />
|
||||
<BuildOutputInPackage Include="bin\$(Configuration)\*\Microsoft.CodeAnalysis*.dll" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
|
|
31
Ryujinx.CustomTasks/SyntaxWalker/ArraySizeCollector.cs
Normal file
31
Ryujinx.CustomTasks/SyntaxWalker/ArraySizeCollector.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.CustomTasks.SyntaxWalker
|
||||
{
|
||||
class ArraySizeCollector : CSharpSyntaxWalker
|
||||
{
|
||||
public ICollection<int> ArraySizes { get; } = new List<int>();
|
||||
|
||||
private void AddArrayString(string name)
|
||||
{
|
||||
if (!name.StartsWith("Array") || !name.Contains("<"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string rawArrayType = name.Split('<')[0];
|
||||
|
||||
if (int.TryParse(rawArrayType.Substring(5), out int size) && !ArraySizes.Contains(size))
|
||||
{
|
||||
ArraySizes.Add(size);
|
||||
}
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.genericnamesyntax?view=roslyn-dotnet-4.3.0
|
||||
public override void VisitGenericName(GenericNameSyntax node) => AddArrayString(node.ToString());
|
||||
// https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.identifiernamesyntax?view=roslyn-dotnet-4.3.0
|
||||
public override void VisitIdentifierName(IdentifierNameSyntax node) => AddArrayString(node.ToString());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue