-
Notifications
You must be signed in to change notification settings - Fork 59
[generator] Encapsulate and parallelize enum generation. #442
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
tools/generator/Java.Interop.Tools.Generator.CodeGeneration/EnumGenerator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using static MonoDroid.Generation.EnumMappings; | ||
|
||
namespace MonoDroid.Generation | ||
{ | ||
class EnumGenerator | ||
{ | ||
protected TextWriter sw; | ||
|
||
public EnumGenerator (TextWriter writer) | ||
{ | ||
sw = writer; | ||
} | ||
|
||
public void WriteEnumeration (KeyValuePair<string, EnumDescription> enu, GenBase [] gens) | ||
{ | ||
string ns = enu.Key.Substring (0, enu.Key.LastIndexOf ('.')).Trim (); | ||
string enoom = enu.Key.Substring (enu.Key.LastIndexOf ('.') + 1).Trim (); | ||
|
||
sw.WriteLine ("namespace {0} {{", ns); | ||
if (enu.Value.BitField) | ||
sw.WriteLine ("\t[System.Flags]"); | ||
sw.WriteLine ("\tpublic enum {0} {{", enoom); | ||
|
||
foreach (var member in enu.Value.Members) { | ||
var managedMember = FindManagedMember (enu.Value, member.Key, gens); | ||
sw.WriteLine ("\t\t[global::Android.Runtime.IntDefinition (" + (managedMember != null ? "\"" + managedMember + "\"" : "null") + ", JniField = \"" + StripExtraInterfaceSpec (enu.Value.JniNames [member.Key]) + "\")]"); | ||
sw.WriteLine ("\t\t{0} = {1},", member.Key.Trim (), member.Value.Trim ()); | ||
} | ||
sw.WriteLine ("\t}"); | ||
sw.WriteLine ("}"); | ||
} | ||
|
||
string FindManagedMember (EnumDescription desc, string enumFieldName, IEnumerable<GenBase> gens) | ||
{ | ||
if (desc.FieldsRemoved) | ||
return null; | ||
|
||
var jniMember = desc.JniNames [enumFieldName]; | ||
if (string.IsNullOrWhiteSpace (jniMember)) { | ||
// enum values like "None" falls here. | ||
return null; | ||
} | ||
return FindManagedMember (jniMember, gens); | ||
} | ||
|
||
WeakReference cache_found_class; | ||
|
||
string FindManagedMember (string jniMember, IEnumerable<GenBase> gens) | ||
{ | ||
string package, type, member; | ||
ParseJniMember (jniMember, out package, out type, out member); | ||
var fullJavaType = (string.IsNullOrEmpty (package) ? "" : package + ".") + type; | ||
|
||
var cls = cache_found_class != null ? cache_found_class.Target as GenBase : null; | ||
if (cls == null || cls.JniName != fullJavaType) { | ||
cls = gens.FirstOrDefault (g => g.JavaName == fullJavaType); | ||
if (cls == null) { | ||
// The class was not found e.g. removed by metadata fixup. | ||
return null; | ||
} | ||
cache_found_class = new WeakReference (cls); | ||
} | ||
var fld = cls.Fields.FirstOrDefault (f => f.JavaName == member); | ||
if (fld == null) { | ||
// The field was not found e.g. removed by metadata fixup. | ||
return null; | ||
} | ||
return cls.FullName + "." + fld.Name; | ||
} | ||
|
||
internal void ParseJniMember (string jniMember, out string package, out string type, out string member) | ||
{ | ||
try { | ||
DoParseJniMember (jniMember, out package, out type, out member); | ||
} catch (Exception ex) { | ||
throw new Exception (string.Format ("failed to parse enum mapping: JNI member: {0}", jniMember, ex)); | ||
} | ||
} | ||
|
||
static void DoParseJniMember (string jniMember, out string package, out string type, out string member) | ||
{ | ||
int endPackage = jniMember.LastIndexOf ('/'); | ||
int endClass = jniMember.LastIndexOf ('.'); | ||
|
||
package = jniMember.Substring (0, endPackage).Replace ('/', '.'); | ||
if (package.StartsWith ("I:")) | ||
package = package.Substring (2); | ||
|
||
if (endClass >= 0) { | ||
type = jniMember.Substring (endPackage + 1, endClass - endPackage - 1).Replace ('$', '.'); | ||
member = jniMember.Substring (endClass + 1); | ||
} else { | ||
type = jniMember.Substring (endPackage + 1).Replace ('$', '.'); | ||
member = ""; | ||
} | ||
} | ||
|
||
string StripExtraInterfaceSpec (string jniFieldSpec) | ||
{ | ||
return jniFieldSpec.StartsWith ("I:", StringComparison.Ordinal) ? jniFieldSpec.Substring (2) : jniFieldSpec; | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 4 additions & 4 deletions
8
tools/generator/Tests-Core/expected.ji/Android.Text.SpanTypes.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
namespace Android.Text { | ||
public enum SpanTypes { | ||
[global::Android.Runtime.IntDefinition (null, JniField = "android/text/Spanned.SPAN_COMPOSING")] | ||
Composing = 256, | ||
} | ||
public enum SpanTypes { | ||
[global::Android.Runtime.IntDefinition (null, JniField = "android/text/Spanned.SPAN_COMPOSING")] | ||
Composing = 256, | ||
} | ||
} |
8 changes: 4 additions & 4 deletions
8
tools/generator/Tests-Core/expected/Android.Text.SpanTypes.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
namespace Android.Text { | ||
public enum SpanTypes { | ||
[global::Android.Runtime.IntDefinition (null, JniField = "android/text/Spanned.SPAN_COMPOSING")] | ||
Composing = 256, | ||
} | ||
public enum SpanTypes { | ||
[global::Android.Runtime.IntDefinition (null, JniField = "android/text/Spanned.SPAN_COMPOSING")] | ||
Composing = 256, | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
tools/generator/Tests/Unit-Tests/EnumGeneratorExpectedResults/WriteBasicEnum.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Android.App { | ||
public enum RecentTaskFlags { | ||
[global::Android.Runtime.IntDefinition (null, JniField = "android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE")] | ||
WithExcluded = 1, | ||
[global::Android.Runtime.IntDefinition (null, JniField = "android/app/ActivityManager.RECENT_WITH_EXCLUDED")] | ||
IgnoreUnavailable = 2, | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
tools/generator/Tests/Unit-Tests/EnumGeneratorExpectedResults/WriteEnumWithGens.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Android.App { | ||
public enum RecentTaskFlags { | ||
[global::Android.Runtime.IntDefinition ("android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE", JniField = "android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE")] | ||
WithExcluded = 1, | ||
[global::Android.Runtime.IntDefinition (null, JniField = "android/app/ActivityManager.RECENT_WITH_EXCLUDED")] | ||
IgnoreUnavailable = 2, | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
tools/generator/Tests/Unit-Tests/EnumGeneratorExpectedResults/WriteFlagsEnum.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Android.App { | ||
[System.Flags] | ||
public enum RecentTaskFlags { | ||
[global::Android.Runtime.IntDefinition (null, JniField = "android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE")] | ||
WithExcluded = 1, | ||
[global::Android.Runtime.IntDefinition (null, JniField = "android/app/ActivityManager.RECENT_WITH_EXCLUDED")] | ||
IgnoreUnavailable = 2, | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Reflection; | ||
using System.Text; | ||
using MonoDroid.Generation; | ||
using NUnit.Framework; | ||
using NUnit.Framework.Internal; | ||
|
||
namespace generatortests.Unit_Tests | ||
{ | ||
[TestFixture] | ||
class EnumGeneratorTests | ||
{ | ||
protected EnumGenerator generator; | ||
protected StringBuilder builder; | ||
protected StringWriter writer; | ||
|
||
[SetUp] | ||
public void SetUp () | ||
{ | ||
builder = new StringBuilder (); | ||
writer = new StringWriter (builder); | ||
|
||
generator = new EnumGenerator (writer); | ||
} | ||
|
||
[Test] | ||
public void WriteBasicEnum () | ||
{ | ||
var enu = CreateEnum (); | ||
enu.Value.FieldsRemoved = true; | ||
|
||
generator.WriteEnumeration (enu, null); | ||
|
||
Assert.AreEqual (GetExpected (nameof (WriteBasicEnum)), writer.ToString ().NormalizeLineEndings ()); | ||
} | ||
|
||
[Test] | ||
public void WriteFlagsEnum () | ||
{ | ||
var enu = CreateEnum (); | ||
enu.Value.BitField = true; | ||
enu.Value.FieldsRemoved = true; | ||
|
||
generator.WriteEnumeration (enu, null); | ||
|
||
Assert.AreEqual (GetExpected (nameof (WriteFlagsEnum)), writer.ToString ().NormalizeLineEndings ()); | ||
} | ||
|
||
[Test] | ||
public void WriteEnumWithGens () | ||
{ | ||
var enu = CreateEnum (); | ||
var gens = CreateGens (); | ||
|
||
generator.WriteEnumeration (enu, gens); | ||
|
||
Assert.AreEqual (GetExpected (nameof (WriteEnumWithGens)), writer.ToString ().NormalizeLineEndings ()); | ||
} | ||
|
||
protected string GetExpected (string testName) | ||
{ | ||
var root = Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location); | ||
|
||
return File.ReadAllText (Path.Combine (root, "Unit-Tests", "EnumGeneratorExpectedResults", $"{testName}.txt")).NormalizeLineEndings (); | ||
} | ||
|
||
KeyValuePair<string, EnumMappings.EnumDescription> CreateEnum () | ||
{ | ||
var enu = new EnumMappings.EnumDescription { | ||
Members = new Dictionary<string, string> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Entirely unrelated, this (This doesn't need to be fixed here; it's simply that I noticed this now.) |
||
{ "WithExcluded", "1" }, | ||
{ "IgnoreUnavailable", "2" } | ||
}, | ||
JniNames = new Dictionary<string, string> { | ||
{ "WithExcluded", "android/app/ActivityManager.RECENT_IGNORE_UNAVAILABLE" }, | ||
{ "IgnoreUnavailable", "android/app/ActivityManager.RECENT_WITH_EXCLUDED" } | ||
}, | ||
BitField = false, | ||
FieldsRemoved = false | ||
}; | ||
|
||
return new KeyValuePair<string, EnumMappings.EnumDescription> ("Android.App.RecentTaskFlags", enu); | ||
} | ||
|
||
GenBase[] CreateGens () | ||
{ | ||
var klass = new TestClass (string.Empty, "android.app.ActivityManager"); | ||
|
||
klass.Fields.Add (new TestField ("int", "RECENT_IGNORE_UNAVAILABLE")); | ||
|
||
return new [] { klass }; | ||
} | ||
} | ||
} |
12 changes: 6 additions & 6 deletions
12
tools/generator/Tests/expected/EnumerationFixup/Xamarin.Test.SomeValues.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
namespace Xamarin.Test { | ||
public enum SomeValues { | ||
[global::Android.Runtime.IntDefinition (null, JniField = "xamarin/test/SomeObject.SOME_VALUE")] | ||
SomeValue = 0, | ||
[global::Android.Runtime.IntDefinition (null, JniField = "xamarin/test/SomeObject.SOME_VALUE2")] | ||
SomeValue2 = 1, | ||
} | ||
public enum SomeValues { | ||
[global::Android.Runtime.IntDefinition (null, JniField = "xamarin/test/SomeObject.SOME_VALUE")] | ||
SomeValue = 0, | ||
[global::Android.Runtime.IntDefinition (null, JniField = "xamarin/test/SomeObject.SOME_VALUE2")] | ||
SomeValue2 = 1, | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that this should be
files.OrderBy (f => f).ToList ()
, or some variation thereof.Rationale: C# deterministic builds. If, for whatever reason,
generator
is re-executed during a build, it would be nice if, when deterministic builds are enabled, the resulting assembly did not change unless the generated code actually changed.The order of generated files appears to be used in the
csc /deterministic
algorithm, and thus altering the order of files may result in the creation of a different assembly. For example:"Simply" changing the order of command-line arguments results in a different assembly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do the sort when we write the project file:
https://github.com/xamarin/java.interop/blob/master/tools/generator/GenerationInfo.cs#L64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I missed that. Thanks!