diff --git a/src/EventBuilder/Cecil/StaticEventTemplateInformation.cs b/src/EventBuilder/Cecil/StaticEventTemplateInformation.cs new file mode 100644 index 0000000000..b59d53d7e7 --- /dev/null +++ b/src/EventBuilder/Cecil/StaticEventTemplateInformation.cs @@ -0,0 +1,135 @@ +using EventBuilder.Entities; +using Mono.Cecil; +using System.Collections.Generic; +using System.Linq; + +namespace EventBuilder.Cecil +{ + public static class StaticEventTemplateInformation + { + private static string GetEventArgsTypeForEvent(EventDefinition ei) + { + // Find the EventArgs type parameter of the event via digging around via reflection + var type = ei.EventType.Resolve(); + var invoke = type.Methods.First(x => x.Name == "Invoke"); + if (invoke.Parameters.Count < 1) return null; + + var param = invoke.Parameters.Count == 1 ? invoke.Parameters[0] : invoke.Parameters[1]; + var ret = param.ParameterType.FullName; + + var generic = ei.EventType as GenericInstanceType; + if (generic != null) + { + foreach ( + var kvp in + type.GenericParameters.Zip(generic.GenericArguments, (name, actual) => new {name, actual})) + { + var realType = GetRealTypeName(kvp.actual); + + ret = ret.Replace(kvp.name.FullName, realType); + } + } + + // NB: Inner types in Mono.Cecil get reported as 'Foo/Bar' + return ret.Replace('/', '.'); + } + + private static string GetRealTypeName(TypeDefinition t) + { + if (t.GenericParameters.Count == 0) return t.FullName; + + var ret = string.Format("{0}<{1}>", + t.Namespace + "." + t.Name, + string.Join(",", t.GenericParameters.Select(x => GetRealTypeName(x.Resolve())))); + + // NB: Inner types in Mono.Cecil get reported as 'Foo/Bar' + return ret.Replace('/', '.'); + } + + private static string GetRealTypeName(TypeReference t) + { + var generic = t as GenericInstanceType; + if (generic == null) return t.FullName; + + var ret = string.Format("{0}<{1}>", + generic.Namespace + "." + generic.Name, + string.Join(",", generic.GenericArguments.Select(x => GetRealTypeName(x)))); + + // NB: Inner types in Mono.Cecil get reported as 'Foo/Bar' + return ret.Replace('/', '.'); + } + + private static EventDefinition[] GetPublicEvents(TypeDefinition t) + { + return + t.Events + + .Where(x => + { + return x.AddMethod.IsPublic && GetEventArgsTypeForEvent(x) != null; + }) + .ToArray(); + } + + public static NamespaceInfo[] Create(AssemblyDefinition[] targetAssemblies) + { + var publicTypesWithEvents = targetAssemblies + .SelectMany(x => SafeTypes.GetSafeTypes(x)) + .Where(x => x.IsPublic && !x.HasGenericParameters) + .Select(x => new {Type = x, Events = GetPublicEvents(x)}) + .Where(x => x.Events.Length > 0) + .ToArray(); + + var garbageNamespaceList = new[] + { + "ReactiveUI.Events" + }; + + var namespaceData = publicTypesWithEvents + .GroupBy(x => x.Type.Namespace) + .Where(x => !garbageNamespaceList.Contains(x.Key)) + .Select(x => new NamespaceInfo + { + Name = x.Key, + Types = x.Select(y => new PublicTypeInfo + { + Name = y.Type.Name, + Type = y.Type, + Events = y.Events.Select(z => new PublicEventInfo + { + Name = z.Name, + EventHandlerType = GetRealTypeName(z.EventType), + EventArgsType = GetEventArgsTypeForEvent(z) + }).ToArray() + }).ToArray() + }).ToArray(); + + foreach (var type in namespaceData.SelectMany(x => x.Types)) + { + var parentWithEvents = GetParents(type.Type).FirstOrDefault(x => GetPublicEvents(x).Any()); + if (parentWithEvents == null) + continue; + + type.Parent = new ParentInfo {Name = parentWithEvents.FullName}; + } + + return namespaceData; + } + + private static IEnumerable GetParents(TypeDefinition type) + { + var current = type.BaseType != null && type.BaseType.ToString() != "System.Object" + ? type.BaseType.Resolve() + : null; + + while (current != null) + { + yield return current.Resolve(); + + current = current.BaseType != null + ? current.BaseType.Resolve() + : null; + } + } + } +} \ No newline at end of file diff --git a/src/EventBuilder/CommandLineOptions.cs b/src/EventBuilder/CommandLineOptions.cs index cb7c8ac3c7..70a180eea3 100644 --- a/src/EventBuilder/CommandLineOptions.cs +++ b/src/EventBuilder/CommandLineOptions.cs @@ -18,7 +18,8 @@ public enum AutoPlatform WPF, XamForms, UWP, - Winforms + Winforms, + Essentials } public class CommandLineOptions diff --git a/src/EventBuilder/EventBuilder.csproj b/src/EventBuilder/EventBuilder.csproj index 750cebbb0b..7ec1ffa494 100644 --- a/src/EventBuilder/EventBuilder.csproj +++ b/src/EventBuilder/EventBuilder.csproj @@ -1,26 +1,28 @@ - - - - Exe - net461 - EventBuilder - EventBuilder - - - - - - PreserveNewest - - - - + + + Exe + net461 + EventBuilder + EventBuilder + + + + + + PreserveNewest + + + PreserveNewest + + + + - + \ No newline at end of file diff --git a/src/EventBuilder/Platforms/Android.cs b/src/EventBuilder/Platforms/Android.cs index 456946908e..a0075e8180 100644 --- a/src/EventBuilder/Platforms/Android.cs +++ b/src/EventBuilder/Platforms/Android.cs @@ -9,6 +9,8 @@ namespace EventBuilder.Platforms { public class Android : BasePlatform { + public override AutoPlatform Platform => AutoPlatform.Android; + public Android(string referenceAssembliesLocation) { if (PlatformHelper.IsRunningOnMono()) { diff --git a/src/EventBuilder/Platforms/BasePlatform.cs b/src/EventBuilder/Platforms/BasePlatform.cs index 58e6328dd0..5711b682a5 100644 --- a/src/EventBuilder/Platforms/BasePlatform.cs +++ b/src/EventBuilder/Platforms/BasePlatform.cs @@ -6,7 +6,7 @@ namespace EventBuilder.Platforms { - public class BasePlatform : IPlatform + public abstract class BasePlatform : IPlatform { public BasePlatform() { @@ -14,6 +14,8 @@ public BasePlatform() CecilSearchDirectories = new List(); } + public abstract AutoPlatform Platform { get; } + public List Assemblies { get; set; } public List CecilSearchDirectories { get; set; } } diff --git a/src/EventBuilder/Platforms/Bespoke.cs b/src/EventBuilder/Platforms/Bespoke.cs index 027d18ac94..ddc63bdb14 100644 --- a/src/EventBuilder/Platforms/Bespoke.cs +++ b/src/EventBuilder/Platforms/Bespoke.cs @@ -6,5 +6,6 @@ namespace EventBuilder.Platforms { public class Bespoke : BasePlatform { + public override AutoPlatform Platform => AutoPlatform.None; } } \ No newline at end of file diff --git a/src/EventBuilder/Platforms/Essentials.cs b/src/EventBuilder/Platforms/Essentials.cs new file mode 100644 index 0000000000..d5af0807eb --- /dev/null +++ b/src/EventBuilder/Platforms/Essentials.cs @@ -0,0 +1,63 @@ +using NuGet; +using Polly; +using Serilog; +using System; +using System.IO; +using System.Linq; + +namespace EventBuilder.Platforms +{ + public class Essentials : BasePlatform + { + private const string _packageName = "Xamarin.Essentials"; + + public override AutoPlatform Platform => AutoPlatform.Essentials; + + public Essentials() + { + var packageUnzipPath = Environment.CurrentDirectory; + + var retryPolicy = Policy + .Handle() + .WaitAndRetry( + 5, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (exception, timeSpan, context) => + { + Log.Warning( + "An exception was thrown whilst retrieving or installing {packageName}: {exception}", + _packageName, exception); + }); + + retryPolicy.Execute(() => + { + var repo = PackageRepositoryFactory.Default.CreateRepository("https://packages.nuget.org/api/v2"); + var packageManager = new PackageManager(repo, packageUnzipPath); + var fpid = packageManager.SourceRepository.FindPackagesById(_packageName); + var package = fpid.Single(x => x.Version.ToString() == "0.9.1-preview"); + + packageManager.InstallPackage(package, true, true); + + Log.Debug("Using Xamarin Essentials {Version} released on {Published}", package.Version, package.Published); + Log.Debug("{ReleaseNotes}", package.ReleaseNotes); + }); + + var xamarinForms = + Directory.GetFiles(packageUnzipPath, + "Xamarin.Essentials.dll", SearchOption.AllDirectories); + + var latestVersion = xamarinForms.First(x => x.Contains("netstandard1.0")); + Assemblies.Add(latestVersion); + + if (PlatformHelper.IsRunningOnMono()) + { + CecilSearchDirectories.Add( + @"/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/xbuild-frameworks/.NETPortable/v4.5/Profile/Profile111"); + } + else + { + CecilSearchDirectories.Add(@"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.5\Profile\Profile111"); + } + } + } +} \ No newline at end of file diff --git a/src/EventBuilder/Platforms/IPlatform.cs b/src/EventBuilder/Platforms/IPlatform.cs index 7c858ecfda..ce5339b398 100644 --- a/src/EventBuilder/Platforms/IPlatform.cs +++ b/src/EventBuilder/Platforms/IPlatform.cs @@ -8,6 +8,8 @@ namespace EventBuilder.Platforms { public interface IPlatform { + AutoPlatform Platform { get; } + List Assemblies { get; set; } // Cecil when run on Mono needs some direction as to the location of the platform specific MSCORLIB. diff --git a/src/EventBuilder/Platforms/Mac.cs b/src/EventBuilder/Platforms/Mac.cs index 4ea8e2cdb0..7407ef6a0d 100644 --- a/src/EventBuilder/Platforms/Mac.cs +++ b/src/EventBuilder/Platforms/Mac.cs @@ -10,6 +10,8 @@ namespace EventBuilder.Platforms // ReSharper disable once InconsistentNaming public class Mac : BasePlatform { + public override AutoPlatform Platform => AutoPlatform.Mac; + public Mac(string referenceAssembliesLocation) { if (PlatformHelper.IsRunningOnMono()) { diff --git a/src/EventBuilder/Platforms/Tizen.cs b/src/EventBuilder/Platforms/Tizen.cs index af4ca08bf3..6f009e62c9 100644 --- a/src/EventBuilder/Platforms/Tizen.cs +++ b/src/EventBuilder/Platforms/Tizen.cs @@ -15,6 +15,8 @@ public class Tizen : BasePlatform { private const string _packageName = "Tizen.NET"; + public override AutoPlatform Platform => AutoPlatform.Tizen; + public Tizen() { var packageUnzipPath = Environment.CurrentDirectory; diff --git a/src/EventBuilder/Platforms/UWP.cs b/src/EventBuilder/Platforms/UWP.cs index ba3e73b44d..5f729dc128 100644 --- a/src/EventBuilder/Platforms/UWP.cs +++ b/src/EventBuilder/Platforms/UWP.cs @@ -8,6 +8,8 @@ namespace EventBuilder.Platforms { public class UWP : BasePlatform { + public override AutoPlatform Platform => AutoPlatform.UWP; + public UWP() { if (PlatformHelper.IsRunningOnMono()) diff --git a/src/EventBuilder/Platforms/WPF.cs b/src/EventBuilder/Platforms/WPF.cs index 5fce05b9d6..ad3f894c99 100644 --- a/src/EventBuilder/Platforms/WPF.cs +++ b/src/EventBuilder/Platforms/WPF.cs @@ -8,6 +8,8 @@ namespace EventBuilder.Platforms { public class WPF : BasePlatform { + public override AutoPlatform Platform => AutoPlatform.WPF; + public WPF() { if (PlatformHelper.IsRunningOnMono()) { diff --git a/src/EventBuilder/Platforms/Winforms.cs b/src/EventBuilder/Platforms/Winforms.cs index db898f9c5a..d0d72e8a77 100644 --- a/src/EventBuilder/Platforms/Winforms.cs +++ b/src/EventBuilder/Platforms/Winforms.cs @@ -8,6 +8,8 @@ namespace EventBuilder.Platforms { public class Winforms : BasePlatform { + public override AutoPlatform Platform => AutoPlatform.Winforms; + public Winforms() { if (PlatformHelper.IsRunningOnMono()) { diff --git a/src/EventBuilder/Platforms/XamForms.cs b/src/EventBuilder/Platforms/XamForms.cs index cb2e434d6f..b9a47656cb 100644 --- a/src/EventBuilder/Platforms/XamForms.cs +++ b/src/EventBuilder/Platforms/XamForms.cs @@ -15,6 +15,8 @@ public class XamForms : BasePlatform { private const string _packageName = "Xamarin.Forms"; + public override AutoPlatform Platform => AutoPlatform.XamForms; + public XamForms() { var packageUnzipPath = Environment.CurrentDirectory; diff --git a/src/EventBuilder/Platforms/iOS.cs b/src/EventBuilder/Platforms/iOS.cs index bc9dd65539..f344ed59b9 100644 --- a/src/EventBuilder/Platforms/iOS.cs +++ b/src/EventBuilder/Platforms/iOS.cs @@ -10,6 +10,8 @@ namespace EventBuilder.Platforms // ReSharper disable once InconsistentNaming public class iOS : BasePlatform { + public override AutoPlatform Platform => AutoPlatform.iOS; + public iOS(string referenceAssembliesLocation) { if (PlatformHelper.IsRunningOnMono()) { diff --git a/src/EventBuilder/Program.cs b/src/EventBuilder/Program.cs index 9957b26894..6f99bc0f49 100644 --- a/src/EventBuilder/Program.cs +++ b/src/EventBuilder/Program.cs @@ -41,7 +41,7 @@ private static void Main(string[] args) // allow app to be debugged in visual studio. if (Debugger.IsAttached) { //args = "--help ".Split(' '); - args = "--platform=tizen".Split(' '); + args = "--platform=essentials".Split(' '); //args = new[] //{ // "--platform=none", @@ -113,6 +113,11 @@ private static void Main(string[] args) platform = new Winforms(); break; + case AutoPlatform.Essentials: + platform = new Essentials(); + _mustacheTemplate = "XamarinEssentialsTemplate.mustache"; + break; + default: throw new ArgumentOutOfRangeException(); } @@ -149,8 +154,18 @@ public static void ExtractEventsFromAssemblies(IPlatform platform) Log.Debug("Using {template} as the mustache template", _mustacheTemplate); var template = File.ReadAllText(_mustacheTemplate, Encoding.UTF8); - var namespaceData = EventTemplateInformation.Create(targetAssemblies); + var namespaceData = new Entities.NamespaceInfo[]{}; + switch (platform.Platform) + { + case AutoPlatform.Essentials: + namespaceData = StaticEventTemplateInformation.Create(targetAssemblies); + break; + default: + namespaceData = EventTemplateInformation.Create(targetAssemblies); + break; + } + var delegateData = DelegateTemplateInformation.Create(targetAssemblies); var result = Render.StringToString(template, diff --git a/src/EventBuilder/XamarinEssentialsTemplate.mustache b/src/EventBuilder/XamarinEssentialsTemplate.mustache new file mode 100644 index 0000000000..577ffde5bf --- /dev/null +++ b/src/EventBuilder/XamarinEssentialsTemplate.mustache @@ -0,0 +1,36 @@ +#pragma warning disable 1591,0618,0105,0672 + +using System; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Subjects; + +{{#Namespaces}} +using {{Name}}; +{{/Namespaces}} +{{#DelegateNamespaces}} +using {{Name}}; +{{/DelegateNamespaces}} + +{{#Namespaces}} +namespace {{Name}} +{ + public static class Events + { + +{{#Types}} +{{#Events}} + public static IObservable<{{EventArgsType}}> {{#Type}}{{Name}}{{/Type}}{{Name}}() + => Observable + .FromEventPattern<{{EventArgsType}}>( + x => {{#Type}}{{Name}}{{/Type}}.{{Name}} += x, + x => {{#Type}}{{Name}}{{/Type}}.{{Name}} -= x) + .Select(x => x.EventArgs); + +{{/Events}} +{{/Types}} + } +} +{{/Namespaces}} + +#pragma warning restore 1591,0618,0105,0672 \ No newline at end of file