Skip to content

Commit e7b5964

Browse files
Merge pull request #109 from DLSinnocence/master
Feat:完善对Mono的支持
2 parents 2abe457 + 58ad233 commit e7b5964

14 files changed

Lines changed: 971 additions & 19 deletions

File tree

.github/workflows/test.yml

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,37 @@ jobs:
3030
7.0.x
3131
8.0.x
3232
9.0.x
33+
- name: Setup Mono
34+
run: choco install mono -y
3335

3436
- name: Restore NuGets
3537
run: dotnet restore ${{ env.SOLUTION }}
3638

3739
- name: Build Solution in DEBUG
38-
run: dotnet build --configuration Debug --no-restore ${{ env.SOLUTION }}
40+
run: dotnet build --configuration Debug --no-restore ${{ env.SOLUTION }} -p:MonoCompat=false
3941

4042
- name: Test in DEBUG
41-
run: dotnet test --configuration Debug --no-build ${{ env.SOLUTION }}
43+
run: dotnet test --configuration Debug --no-build ${{ env.SOLUTION }} -p:MonoCompat=false --filter "FullyQualifiedName!~Rougamo.Fody.Tests.Mono48."
4244

4345
- name: Build Solution in RELEASE
44-
run: dotnet build --configuration Release --no-restore ${{ env.SOLUTION }}
46+
run: dotnet build --configuration Release --no-restore ${{ env.SOLUTION }} -p:MonoCompat=false
4547

4648
- name: Test in RELEASE
47-
run: dotnet test --configuration Release --no-build ${{ env.SOLUTION }}
49+
run: dotnet test --configuration Release --no-build ${{ env.SOLUTION }} -p:MonoCompat=false --filter "FullyQualifiedName!~Rougamo.Fody.Tests.Mono48."
50+
51+
- name: Build Solution in Debug(Mono)
52+
run: dotnet build --configuration Debug --no-restore ${{ env.SOLUTION }} -p:MonoCompat=true
53+
54+
- name: Test in DEBUG(Mono)
55+
run: dotnet test --configuration Debug --no-build test/Rougamo.Fody.Tests/Rougamo.Fody.Tests.csproj -f net48 --filter "FullyQualifiedName~Rougamo.Fody.Tests.Mono48." -p:MonoCompat=true
56+
57+
- name: Build Solution in Release(Mono)
58+
run: dotnet build --configuration Release --no-restore ${{ env.SOLUTION }} -p:MonoCompat=true
59+
60+
- name: Test in Release(Mono)
61+
run: dotnet test --configuration Release --no-build test/Rougamo.Fody.Tests/Rougamo.Fody.Tests.csproj -f net48 --filter "FullyQualifiedName~Rougamo.Fody.Tests.Mono48." -p:MonoCompat=true
62+
63+
4864

4965
linux:
5066
name: Linux
@@ -80,7 +96,7 @@ jobs:
8096
run: dotnet restore ${{ env.SOLUTION }}
8197

8298
- name: Build Solution in DEBUG
83-
run: dotnet build --configuration Debug --no-restore ${{ env.SOLUTION }}
99+
run: dotnet build --configuration Debug --no-restore ${{ env.SOLUTION }} -p:MonoCompat=false
84100

85101
- name: Test in DEBUG with netcoreapp3.1
86102
run: dotnet test --configuration Debug --no-build --framework netcoreapp3.1 test/Rougamo.Fody.Tests/Rougamo.Fody.Tests.csproj
@@ -94,14 +110,11 @@ jobs:
94110
- name: Test in DEBUG with net9.0
95111
run: dotnet test --configuration Debug --no-build --framework net9.0 test/Rougamo.Fody.Tests/Rougamo.Fody.Tests.csproj
96112

97-
- name: Test Fody in DEBUG with Mono (net48)
98-
run: mono ~/.nuget/packages/xunit.runner.console/2.4.2/tools/net472/xunit.console.exe test/Rougamo.Fody.Tests/bin/Debug/net48/Rougamo.Fody.Tests.dll
99-
100113
- name: Test Analyzers in DEBUG with net8.0
101114
run: dotnet test --configuration Debug --no-build --framework net8.0 test/Rougamo.Analyzers.Tests/Rougamo.Analyzers.Tests.csproj
102115

103116
- name: Build Solution in RELEASE
104-
run: dotnet build --configuration Release --no-restore ${{ env.SOLUTION }}
117+
run: dotnet build --configuration Release --no-restore ${{ env.SOLUTION }} -p:MonoCompat=false
105118

106119
- name: Test in RELEASE with netcoreapp3.1
107120
run: dotnet test --configuration Release --no-build --framework netcoreapp3.1 test/Rougamo.Fody.Tests/Rougamo.Fody.Tests.csproj
@@ -115,8 +128,5 @@ jobs:
115128
- name: Test in RELEASE with net9.0
116129
run: dotnet test --configuration Release --no-build --framework net9.0 test/Rougamo.Fody.Tests/Rougamo.Fody.Tests.csproj
117130

118-
- name: Test Fody in RELEASE with Mono (net48)
119-
run: mono ~/.nuget/packages/xunit.runner.console/2.4.2/tools/net472/xunit.console.exe test/Rougamo.Fody.Tests/bin/Release/net48/Rougamo.Fody.Tests.dll
120-
121131
- name: Test in RELEASE with net8.0
122132
run: dotnet test --configuration Release --no-build --framework net8.0 test/Rougamo.Analyzers.Tests/Rougamo.Analyzers.Tests.csproj

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,4 @@ ASALocalRun/
328328

329329
# MFractors (Xamarin productivity tool) working folder
330330
.mfractor/
331+

src/Rougamo.Fody/FindRous.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Fody;
1+
using Fody;
22
using Mono.Cecil;
33
using Mono.Cecil.Cil;
44
using Mono.Cecil.Rocks;
@@ -272,6 +272,11 @@ private GlobalMos FindGlobalAttributes(Collection<CustomAttribute> attributes, s
272272
var moTypeRef = genericTypeRefs![0];
273273
generics.TryAdd(moTypeRef.FullName, moTypeRef);
274274
}
275+
else if (attrType.Is(Constants.TYPE_RougamoAttribute))
276+
{
277+
var moTypeRef = (TypeReference)attribute.ConstructorArguments[0].Value;
278+
generics.TryAdd(moTypeRef.FullName, moTypeRef);
279+
}
275280
else if (attrType.Is(Constants.TYPE_MoProxyAttribute))
276281
{
277282
var origin = (TypeReference)attribute.ConstructorArguments[0].Value;

src/Rougamo/Context/MethodContext.cs

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
using System;
1+
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Linq;
45
using System.Reflection;
56
using System.Threading.Tasks;
67

@@ -13,6 +14,8 @@ public class MethodContext : IResettable
1314
{
1415
private Type? _taskReturnType;
1516
private IDictionary? _datas;
17+
private MethodBase _method = null!;
18+
private bool _methodResolved;
1619

1720
/// <summary>
1821
/// User-defined state data.
@@ -40,7 +43,23 @@ public class MethodContext : IResettable
4043
/// Current method information.
4144
/// </summary>
4245
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
43-
public MethodBase Method { get; set; }
46+
public MethodBase Method
47+
{
48+
get
49+
{
50+
if (!_methodResolved)
51+
{
52+
_method = ResolveMethod(_method);
53+
_methodResolved = true;
54+
}
55+
return _method;
56+
}
57+
set
58+
{
59+
_method = value;
60+
_methodResolved = false;
61+
}
62+
}
4463
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
4564

4665
/// <summary>
@@ -182,5 +201,79 @@ public bool TryReset()
182201

183202
return true;
184203
}
204+
205+
private static MethodBase ResolveMethod(MethodBase method)
206+
{
207+
if (method == null) return null!;
208+
209+
method = ResolveStateMachineMethod(method);
210+
method = ResolveRougamoProxyMethod(method);
211+
return method;
212+
}
213+
214+
private static MethodBase ResolveStateMachineMethod(MethodBase method)
215+
{
216+
if (method.Name != "MoveNext") return method;
217+
218+
var stateMachineType = method.DeclaringType;
219+
var declaringType = stateMachineType?.DeclaringType;
220+
if (stateMachineType == null || declaringType == null) return method;
221+
222+
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
223+
foreach (var candidate in declaringType.GetMethods(flags))
224+
{
225+
var attributes = candidate.GetCustomAttributes(inherit: false);
226+
foreach (var attribute in attributes)
227+
{
228+
var attrType = attribute.GetType();
229+
var fullName = attrType.FullName;
230+
if (fullName != "System.Runtime.CompilerServices.AsyncStateMachineAttribute" &&
231+
fullName != "System.Runtime.CompilerServices.IteratorStateMachineAttribute" &&
232+
fullName != "System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute")
233+
{
234+
continue;
235+
}
236+
237+
var stateMachineTypeProperty = attrType.GetProperty("StateMachineType", BindingFlags.Public | BindingFlags.Instance);
238+
if (stateMachineTypeProperty?.GetValue(attribute) is Type candidateStateMachineType &&
239+
candidateStateMachineType == stateMachineType)
240+
{
241+
return candidate;
242+
}
243+
}
244+
}
245+
246+
return method;
247+
}
248+
249+
private static MethodBase ResolveRougamoProxyMethod(MethodBase method)
250+
{
251+
const string prefix = "$Rougamo_";
252+
if (!method.Name.StartsWith(prefix, StringComparison.Ordinal)) return method;
253+
254+
var declaringType = method.DeclaringType;
255+
if (declaringType == null) return method;
256+
257+
var expectedName = method.Name.Substring(prefix.Length);
258+
var methodGenericCount = method.IsGenericMethod ? method.GetGenericArguments().Length : 0;
259+
var parameterCount = method.GetParameters().Length;
260+
261+
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
262+
var candidates = declaringType.GetMethods(flags)
263+
.Where(m => m.Name == expectedName)
264+
.Where(m => m.GetParameters().Length == parameterCount)
265+
.ToArray();
266+
267+
foreach (var candidate in candidates)
268+
{
269+
var candidateGenericCount = candidate.IsGenericMethod ? candidate.GetGenericArguments().Length : 0;
270+
if (candidateGenericCount == methodGenericCount)
271+
{
272+
return candidate;
273+
}
274+
}
275+
276+
return candidates.FirstOrDefault() ?? method;
277+
}
185278
}
186279
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
param(
2+
[string]$ProjectRoot = $(Split-Path -Parent $PSScriptRoot),
3+
[string]$Configuration = "Debug"
4+
)
5+
6+
$ErrorActionPreference = "Stop"
7+
8+
$items = @(
9+
@{ Namespace = "Rougamo.Fody.Tests.Mono48"; Class = "MonoTests"; TargetType = "Rougamo.Fody.Tests.MonoTests"; SourceFile = "MonoTests.cs" },
10+
@{ Namespace = "Rougamo.Fody.Tests.Mono48"; Class = "BasicTest"; TargetType = "Rougamo.Fody.Tests.BasicTest"; SourceFile = "BasicTest.cs" },
11+
@{ Namespace = "Rougamo.Fody.Tests.Mono48"; Class = "IssueTest"; TargetType = "Rougamo.Fody.Tests.IssueTest"; SourceFile = "IssueTest.cs" },
12+
@{ Namespace = "Rougamo.Fody.Tests.Mono48"; Class = "ConfiguredMoTests"; TargetType = "Rougamo.Fody.Tests.ConfiguredMoTests"; SourceFile = "ConfiguredMoTests.cs" },
13+
@{ Namespace = "Rougamo.Fody.Tests.Mono48"; Class = "PatternTests"; TargetType = "Rougamo.Fody.Tests.PatternTests"; SourceFile = "PatternTests.cs" },
14+
15+
@{ Namespace = "Rougamo.Fody.Tests.Mono48.Signatures"; Class = "SignatureBasicTests"; TargetType = "Rougamo.Fody.Tests.Signatures.SignatureBasicTests"; SourceFile = "Signatures/SignatureBasicTests.cs" },
16+
@{ Namespace = "Rougamo.Fody.Tests.Mono48.Signatures"; Class = "WildcardMatchTests"; TargetType = "Rougamo.Fody.Tests.Signatures.WildcardMatchTests"; SourceFile = "Signatures/WildcardMatchTests.cs" },
17+
@{ Namespace = "Rougamo.Fody.Tests.Mono48.Signatures"; Class = "PropertyMatchTests"; TargetType = "Rougamo.Fody.Tests.Signatures.PropertyMatchTests"; SourceFile = "Signatures/PropertyMatchTests.cs" },
18+
@{ Namespace = "Rougamo.Fody.Tests.Mono48.Signatures"; Class = "GetterMatchTests"; TargetType = "Rougamo.Fody.Tests.Signatures.GetterMatchTests"; SourceFile = "Signatures/PropertyMatchTests.cs" },
19+
@{ Namespace = "Rougamo.Fody.Tests.Mono48.Signatures"; Class = "SetterMatchTests"; TargetType = "Rougamo.Fody.Tests.Signatures.SetterMatchTests"; SourceFile = "Signatures/PropertyMatchTests.cs" },
20+
@{ Namespace = "Rougamo.Fody.Tests.Mono48.Signatures"; Class = "PatternParseTests"; TargetType = "Rougamo.Fody.Tests.Signatures.PatternParseTests"; SourceFile = "Signatures/PatternParseTests.cs" },
21+
@{ Namespace = "Rougamo.Fody.Tests.Mono48.Signatures"; Class = "ExecutionMatchTests"; TargetType = "Rougamo.Fody.Tests.Signatures.ExecutionMatchTests"; SourceFile = "Signatures/ExecutionMatchTests.cs" },
22+
@{ Namespace = "Rougamo.Fody.Tests.Mono48.Signatures"; Class = "MethodMatchTests"; TargetType = "Rougamo.Fody.Tests.Signatures.MethodMatchTests"; SourceFile = "Signatures/ExecutionMatchTests.cs" }
23+
)
24+
25+
function Test-PreprocessorCondition {
26+
param(
27+
[string]$Expression,
28+
[bool]$IsDebug
29+
)
30+
31+
$exp = ($Expression -replace '\s+', '')
32+
switch ($exp) {
33+
"DEBUG" { return $IsDebug }
34+
"!DEBUG" { return -not $IsDebug }
35+
default { return $true }
36+
}
37+
}
38+
39+
function Get-ActiveContent {
40+
param(
41+
[string]$Content,
42+
[bool]$IsDebug
43+
)
44+
45+
$lines = $Content -split "`r?`n"
46+
$states = New-Object System.Collections.Generic.List[bool]
47+
$states.Add($true)
48+
$sb = New-Object System.Text.StringBuilder
49+
50+
foreach ($line in $lines) {
51+
if ($line -match '^\s*#if\s+(.+)$') {
52+
$parent = $states[$states.Count - 1]
53+
$condition = Test-PreprocessorCondition -Expression $matches[1] -IsDebug $IsDebug
54+
$states.Add($parent -and $condition)
55+
continue
56+
}
57+
58+
if ($line -match '^\s*#else\b') {
59+
if ($states.Count -gt 1) {
60+
$parent = $states[$states.Count - 2]
61+
$current = $states[$states.Count - 1]
62+
$states[$states.Count - 1] = $parent -and (-not $current)
63+
}
64+
continue
65+
}
66+
67+
if ($line -match '^\s*#endif\b') {
68+
if ($states.Count -gt 1) {
69+
$states.RemoveAt($states.Count - 1)
70+
}
71+
continue
72+
}
73+
74+
if ($states[$states.Count - 1]) {
75+
[void]$sb.AppendLine($line)
76+
}
77+
}
78+
79+
return $sb.ToString()
80+
}
81+
82+
$isDebug = $Configuration -imatch 'debug'
83+
84+
$factPattern = '(?ms)\[Fact(?:\([^\)]*\))?\]\s*public\s+(?:async\s+)?(?:Task|void)\s+([A-Za-z_][A-Za-z0-9_]*)\s*\('
85+
$grouped = [ordered]@{}
86+
87+
foreach ($item in $items) {
88+
$sourcePath = Join-Path $ProjectRoot $item.SourceFile
89+
$content = Get-Content -Path $sourcePath -Raw -Encoding UTF8
90+
$content = Get-ActiveContent -Content $content -IsDebug $isDebug
91+
$names = [regex]::Matches($content, $factPattern) | ForEach-Object { $_.Groups[1].Value } | Select-Object -Unique
92+
93+
if (-not $grouped.Contains($item.Namespace)) {
94+
$grouped[$item.Namespace] = @()
95+
}
96+
$grouped[$item.Namespace] += [pscustomobject]@{
97+
Class = $item.Class
98+
TargetType = $item.TargetType
99+
Methods = $names
100+
}
101+
}
102+
103+
$sb = New-Object System.Text.StringBuilder
104+
[void]$sb.AppendLine("#if NET48 && MONO_COMPAT")
105+
[void]$sb.AppendLine("using Xunit;")
106+
[void]$sb.AppendLine("")
107+
[void]$sb.AppendLine("// <auto-generated>")
108+
[void]$sb.AppendLine("// Generated by Mono48/Generate-Mono48Mirror.ps1")
109+
[void]$sb.AppendLine("// </auto-generated>")
110+
[void]$sb.AppendLine("")
111+
112+
foreach ($ns in $grouped.Keys) {
113+
[void]$sb.AppendLine("namespace $ns")
114+
[void]$sb.AppendLine("{")
115+
foreach ($entry in $grouped[$ns]) {
116+
[void]$sb.AppendLine(" [Collection(""Mono48"")]")
117+
[void]$sb.AppendLine(" public class $($entry.Class)")
118+
[void]$sb.AppendLine(" {")
119+
foreach ($method in $entry.Methods) {
120+
$factFullName = "$($entry.TargetType).$method"
121+
[void]$sb.AppendLine(" [Fact] public void $method() => MonoRunnerBridge.RunMonoFact(""$factFullName"");")
122+
}
123+
[void]$sb.AppendLine(" }")
124+
[void]$sb.AppendLine("")
125+
}
126+
[void]$sb.AppendLine("}")
127+
[void]$sb.AppendLine("")
128+
}
129+
130+
[void]$sb.AppendLine("#endif")
131+
132+
$outputPath = Join-Path $PSScriptRoot "Mono48MirrorTests.g.cs"
133+
[System.IO.File]::WriteAllText($outputPath, $sb.ToString(), (New-Object System.Text.UTF8Encoding($false)))
134+
Write-Host "Generated $outputPath"

0 commit comments

Comments
 (0)