Skip to content

Commit

Permalink
[dotnet] preliminary cut for class-redirector (#17951)
Browse files Browse the repository at this point in the history
This is the preliminary version of class-redirector addressing issue
#16671
  • Loading branch information
stephen-hawley authored Apr 3, 2023
1 parent 098d9c5 commit bef5d47
Show file tree
Hide file tree
Showing 12 changed files with 707 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/ObjCRuntime/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,15 @@ public bool IsSimulator {

internal static unsafe InitializationOptions* options;

#if NET
public static class ClassHandles
{
internal static unsafe void InitializeClassHandles (MTClassMap* map)
{
}
}
#endif

#if NET
[BindingImpl (BindingImplOptions.Optimizable)]
internal unsafe static bool IsCoreCLR {
Expand Down
2 changes: 2 additions & 0 deletions tools/class-redirector/class-redirector-tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
global using NUnit.Framework;

93 changes: 93 additions & 0 deletions tools/class-redirector/class-redirector-tests/XmlTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using ClassRedirector;
using System.Xml.Linq;

namespace ClassRedirectorTests;

public class XmlTests {
[Test]
public void WritesCorrectXml ()
{
var map = new CSToObjCMap () {
["Foo"] = new ObjCNameIndex ("Bar", 2),
["Baz"] = new ObjCNameIndex ("Zed", 3),
};

var doc = CSToObjCMap.ToXDocument (map);

var elem = doc.Elements ().Where (el => el.Name == "CSToObjCMap").FirstOrDefault ();
Assert.IsNotNull (elem, "no map");
Assert.That (elem.Elements ().Count (), Is.EqualTo (2), "Incorrect number of children");

var cselem = XElementWithAttribute (elem, "CSName", "Foo");
Assert.IsNotNull (cselem, "missing Foo elem");
var nameElem = NameElem (cselem);
Assert.IsNotNull (nameElem, "no name elem1");
Assert.That (nameElem.Value, Is.EqualTo ("Bar"));
var indexElem = IndexElem (cselem);
Assert.IsNotNull (indexElem, "no value elem1");
Assert.That ((int) indexElem, Is.EqualTo (2));

cselem = XElementWithAttribute (elem, "CSName", "Baz");
Assert.IsNotNull (cselem, "missing Baz elem");
nameElem = NameElem (cselem);
Assert.IsNotNull (nameElem, "no name elem2");
indexElem = IndexElem (cselem);
Assert.That (nameElem.Value, Is.EqualTo ("Zed"));
Assert.IsNotNull (indexElem, "no value elem2");
Assert.That ((int) indexElem, Is.EqualTo (3));
}

[Test]
public void ReadsCorrectXml ()
{
var text = @"<?xml version=""1.0"" encoding=""utf-8""?>
<CSToObjCMap>
<Element CSName=""Foo"">
<ObjNameIndex>
<Name>Bar</Name>
<Index>2</Index>
</ObjNameIndex>
</Element>
<Element CSName=""Baz"">
<ObjNameIndex>
<Name>Zed</Name>
<Index>3</Index>
</ObjNameIndex>
</Element>
</CSToObjCMap>";

using var reader = new StringReader (text);
var doc = XDocument.Load (reader);

var map = CSToObjCMap.FromXDocument (doc);
Assert.IsNotNull (map, "no map");
Assert.That (map.Count (), Is.EqualTo (2));

Assert.True (map.TryGetValue ("Foo", out var nameIndex), "no nameIndex");
Assert.That (nameIndex.ObjCName, Is.EqualTo ("Bar"), "no bar name");
Assert.That (nameIndex.MapIndex, Is.EqualTo (2), "no bar index");

Assert.True (map.TryGetValue ("Baz", out var nameIndex1));
Assert.That (nameIndex1.ObjCName, Is.EqualTo ("Zed"), "no bar name");
Assert.That (nameIndex1.MapIndex, Is.EqualTo (3), "no bar index");
}


static XElement? XElementWithAttribute (XElement el, string attrName, string attrValue)
{
return el.Elements ().Where (e => {
var attr = e.Attribute (attrName);
return attr is not null && attr.Value == attrValue;
}).FirstOrDefault ();
}

static XElement? NameElem (XElement el) => FirstElementNamed (el, "Name");

static XElement? IndexElem (XElement el) => FirstElementNamed (el, "Index");

static XElement? FirstElementNamed (XElement el, string name)
{
return el.Descendants ().Where (e => e.Name == name).FirstOrDefault ();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>class_redirector_tests</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\..\common\CSToObjCMap.cs">
<Link>CSToObjCMap.cs</Link>
</Compile>
<Compile Include="..\..\common\ObjCNameIndex.cs">
<Link>ObjCNameIndex.cs</Link>
</Compile>
<Compile Include="..\..\common\StaticRegistrarFile.cs">
<Link>StaticRegistrarFile.cs</Link>
</Compile>
</ItemGroup>
</Project>
59 changes: 59 additions & 0 deletions tools/class-redirector/class-redirector.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 25.0.1705.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "class-redirector", "class-redirector\class-redirector.csproj", "{0723580E-2C56-4460-BDDD-DEF38E0F543F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "class-redirector-tests", "class-redirector-tests\class-redirector-tests.csproj", "{16FD4B5E-5537-46A2-B573-0424A51472AA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0723580E-2C56-4460-BDDD-DEF38E0F543F}.Release|Any CPU.Build.0 = Release|Any CPU
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16FD4B5E-5537-46A2-B573-0424A51472AA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {ECD11F77-045E-4B88-BF92-28ED04BBD631}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
$0.TextStylePolicy = $1
$1.FileWidth = 80
$1.TabWidth = 8
$1.IndentWidth = 8
$1.scope = text/x-csharp
$0.CSharpFormattingPolicy = $2
$2.IndentSwitchSection = False
$2.NewLinesForBracesInTypes = False
$2.NewLinesForBracesInProperties = False
$2.NewLinesForBracesInAccessors = False
$2.NewLinesForBracesInAnonymousMethods = False
$2.NewLinesForBracesInControlBlocks = False
$2.NewLinesForBracesInAnonymousTypes = False
$2.NewLinesForBracesInObjectCollectionArrayInitializers = False
$2.NewLinesForBracesInLambdaExpressionBody = False
$2.NewLineForElse = False
$2.NewLineForCatch = False
$2.NewLineForFinally = False
$2.NewLineForMembersInObjectInit = False
$2.NewLineForMembersInAnonymousTypes = False
$2.NewLineForClausesInQuery = False
$2.SpacingAfterMethodDeclarationName = True
$2.SpaceAfterMethodCallName = True
$2.SpaceBeforeOpenSquareBracket = True
$2.scope = text/x-csharp
EndGlobalSection
EndGlobal
106 changes: 106 additions & 0 deletions tools/class-redirector/class-redirector/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System.IO;
using Mono.Options;
using System.Xml.Linq;

#nullable enable

namespace ClassRedirector {
public class Program {
public static int Main (string [] args)
{
try {
return Main2 (args);
} catch (Exception e) {
Console.Error.WriteLine (e);
return 1;
}
}

public static int Main2 (string [] args)
{
var doHelp = false;
string inputDirectory = "";
var options = new OptionSet () {
{ "h|?|help", o => doHelp = true },
{ "i=|input-directory=", d => inputDirectory = d },
};

if (doHelp) {
options.WriteOptionDescriptions (Console.Out);
Console.WriteLine ($"This program takes an input directory and looks for the file '{StaticRegistrarFile.Name}'.");
Console.WriteLine ("Upon finding it, it uses that as a map for finding all C# classes defined in dll's in the");
Console.WriteLine ("directory and rewrites the classes' definition and usage of class_ptr to be more");
Console.WriteLine ("efficient and, if possible, to no longer need a static constructor.");
Console.WriteLine ("This program also requires that the directory contains one of the Microsoft platform dlls.");
Console.WriteLine ("Classes in the directory are modified in place.");
return 0;
}

if (String.IsNullOrEmpty (inputDirectory)) {
throw new Exception ($"input-directory is required");
}

if (!Directory.Exists (inputDirectory)) {
throw new Exception ($"input-directory {inputDirectory} does not exist.");
}

if (!DirectoryIsWritable (inputDirectory)) {
throw new Exception ($"input-directory {inputDirectory} is not writable");
}

var registrarFile = Path.Combine (inputDirectory, StaticRegistrarFile.Name);
if (!File.Exists (registrarFile)) {
throw new Exception ($"map file {registrarFile} does not exist.");
}

var dllsToProcess = CollectDlls (inputDirectory);
var xamarinDll = FindXamarinDll (dllsToProcess);

if (xamarinDll is null)
throw new Exception ($"unable to find platform dll in {inputDirectory}");

var map = ReadRegistrarFile (registrarFile);

var rewriter = new Rewriter (map, xamarinDll, dllsToProcess);
rewriter.Process ();

return 0;
}

static bool DirectoryIsWritable (string path)
{
var info = new DirectoryInfo (path);
return !info.Attributes.HasFlag (FileAttributes.ReadOnly);
}

static string [] CollectDlls (string dir)
{
return Directory.GetFiles (dir, "*.dll"); // GetFiles returns full paths
}

static string [] xamarinDlls = new string [] {
"Microsoft.iOS.dll",
"Microsoft.macOS.dll",
"Microsoft.tvOS.dll",
};

static bool IsXamarinDll (string p)
{
return xamarinDlls.FirstOrDefault (dll => p.EndsWith (dll, StringComparison.Ordinal)) is not null;
}

static string? FindXamarinDll (string [] paths)
{
return paths.FirstOrDefault (IsXamarinDll);
}

static CSToObjCMap ReadRegistrarFile (string path)
{
var doc = XDocument.Load (path);
var map = CSToObjCMap.FromXDocument (doc);
if (map is null)
throw new Exception ($"Unable to read static registrar map file {path}");
return map;
}
}
}
Loading

6 comments on commit bef5d47

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

Please sign in to comment.