Skip to content

Commit ffc831a

Browse files
committed
(GH-17) Create assembly loader to support dynamic addins
1 parent fc22d28 commit ffc831a

File tree

2 files changed

+242
-0
lines changed

2 files changed

+242
-0
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
using System.Reflection;
2+
using Cake.Core.Annotations;
3+
4+
public class AddinData
5+
{
6+
private readonly ICakeContext _context;
7+
8+
public AddinData(ICakeContext context, string packageName, string packageVersion, string assemblyName = null)
9+
{
10+
this._context = context;
11+
this.Initialize(context, packageName, packageVersion, assemblyName);
12+
}
13+
14+
public Assembly AddinAssembly { get; private set; }
15+
public IList<Type> _declaredEnums = new List<Type>();
16+
public IList<TypeInfo> _definedClasses = new List<TypeInfo>();
17+
private IList<MethodInfo> _definedMethods = new List<MethodInfo>();
18+
19+
public object CreateClass(string classTypeString, params object[] parameters)
20+
{
21+
var possibleClass = _definedClasses.FirstOrDefault(c => string.Compare(c.Name, classTypeString, StringComparison.OrdinalIgnoreCase) == 0);
22+
23+
if (possibleClass is null)
24+
{
25+
throw new NullReferenceException($"No loaded class named {classTypeString} was found in this assembly.");
26+
}
27+
28+
return CreateClass(possibleClass, parameters);
29+
}
30+
31+
public object CreateClass(TypeInfo classType, params object[] parameters)
32+
{
33+
parameters = parameters ?? new object[0];
34+
var constructors = classType.DeclaredConstructors.Where(c => c.IsPublic && !c.IsStatic && c.GetParameters().Length == parameters.Length);
35+
ConstructorInfo constructor = null;
36+
37+
foreach (var ctx in constructors)
38+
{
39+
var ctxParams = ctx.GetParameters();
40+
bool useCtx = true;
41+
for (int i = 0; i < ctxParams.Length && useCtx; i++)
42+
{
43+
useCtx = ctxParams[i].ParameterType == parameters[i].GetType();
44+
}
45+
46+
if (useCtx)
47+
{
48+
constructor = ctx;
49+
break;
50+
}
51+
}
52+
53+
if (constructor is null)
54+
{
55+
throw new NullReferenceException("No valid constructor was found!");
56+
}
57+
58+
return constructor.Invoke(parameters ?? new object[0]);
59+
}
60+
61+
public object CallStaticMethod(string methodName, params object[] parameters)
62+
{
63+
parameters = parameters ?? new object[0];
64+
65+
for (int i = 0; i < parameters.Length; i++)
66+
{
67+
var parameterType = parameters[i].GetType();
68+
int index = parameterType == typeof(string) ? parameters[i].ToString().IndexOf('.') : -1;
69+
if (index >= 0)
70+
{
71+
var enumOrClass = parameters[i].ToString().Substring(0, index);
72+
var enumType = _declaredEnums.FirstOrDefault(e => string.Compare(e.Name, enumOrClass, StringComparison.OrdinalIgnoreCase) == 0);
73+
if (enumType is object)
74+
{
75+
var value = parameters[i].ToString().Substring(index+1);
76+
parameters[i] = Enum.Parse(enumType, value);
77+
}
78+
}
79+
}
80+
81+
var methods = this._definedMethods.Where(m => m.IsPublic && m.IsStatic && string.Compare(m.Name, methodName, StringComparison.OrdinalIgnoreCase) == 0);
82+
MethodInfo method = null;
83+
84+
foreach (var m in methods.Where(m => m.GetParameters().Length == parameters.Length))
85+
{
86+
var methodParams = m.GetParameters();
87+
bool useMethod = true;
88+
89+
for (int i = 0; i < methodParams.Length && useMethod; i++)
90+
{
91+
var methodParamType = methodParams[i].ParameterType;
92+
var optionParamType = parameters[i].GetType();
93+
if (methodParamType.IsEnum && optionParamType == typeof(string))
94+
{
95+
try
96+
{
97+
var parsedValue = Enum.Parse(methodParamType, parameters[i].ToString());
98+
if (parsedValue is object)
99+
{
100+
parameters[i] = parsedValue;
101+
}
102+
else
103+
useMethod = false;
104+
}
105+
catch
106+
{
107+
useMethod = false;
108+
}
109+
}
110+
else if (methodParamType == typeof(Enum) && optionParamType.IsEnum)
111+
{
112+
useMethod = true;
113+
}
114+
else
115+
{
116+
useMethod = methodParamType == optionParamType || methodParamType.IsAssignableFrom(optionParamType);
117+
}
118+
}
119+
120+
if (useMethod)
121+
{
122+
method = m;
123+
break;
124+
}
125+
}
126+
127+
if (method is null)
128+
{
129+
throw new NullReferenceException("No method with the specified name was found!");
130+
}
131+
132+
return method.Invoke(null, parameters);
133+
}
134+
135+
protected void Initialize(ICakeContext context, string packageName, string packageVersion, string assemblyName = null)
136+
{
137+
if (string.IsNullOrEmpty(assemblyName))
138+
{
139+
assemblyName = packageName;
140+
}
141+
142+
var assembly = LoadAddinAssembly(context, packageName, packageVersion, assemblyName);
143+
144+
if (assembly is null)
145+
{
146+
return;
147+
}
148+
149+
AddinAssembly = assembly;
150+
151+
foreach (var ti in assembly.DefinedTypes.Where(ti => ti.IsPublic))
152+
{
153+
if (ti.IsEnum)
154+
{
155+
_declaredEnums.Add(ti.AsType());
156+
}
157+
else if(ti.IsClass && (!ti.IsAbstract || ti.IsStatic()) && !ti.IsGenericTypeDefinition)
158+
{
159+
_definedClasses.Add(ti);
160+
ParseClass(context, ti);
161+
}
162+
}
163+
}
164+
165+
protected void ParseClass(ICakeContext context, TypeInfo classTypeInfo)
166+
{
167+
var aliases = new List<MethodInfo>();
168+
var methods = new List<MethodInfo>();
169+
170+
foreach (var mi in classTypeInfo.DeclaredMethods.Where(i => i.IsPublic))
171+
{
172+
_definedMethods.Add(mi);
173+
}
174+
}
175+
176+
private static Assembly LoadAddinAssembly(ICakeContext context, string packageName, string packageVersion, string assemblyName)
177+
{
178+
var dllPath = GetAssemblyPath(context, packageName, packageVersion, assemblyName);
179+
180+
if (dllPath is null)
181+
{
182+
var script = $"#tool nuget:?package={packageName}&version={packageVersion}&prerelease";
183+
var tempFile = Guid.NewGuid() + ".cake";
184+
185+
System.IO.File.WriteAllText(tempFile, script);
186+
187+
try
188+
{
189+
context.CakeExecuteScript(tempFile);
190+
}
191+
finally
192+
{
193+
if (context.FileExists(tempFile))
194+
{
195+
context.DeleteFile(tempFile);
196+
}
197+
}
198+
}
199+
200+
dllPath = GetAssemblyPath(context, packageName, packageVersion, assemblyName);
201+
202+
if (dllPath is null)
203+
{
204+
context.Warning("Unable to find path to the {0} package assembly!", packageName);
205+
return null;
206+
}
207+
208+
var assembly = Assembly.LoadFrom(dllPath.FullPath);
209+
return assembly;
210+
}
211+
212+
private static FilePath GetAssemblyPath(ICakeContext context, string packageName, string packageVersion, string assemblyName)
213+
{
214+
FilePath dllPath = null;
215+
const string pathFormat = "{0}.{1}/**/{2}*/{3}.dll";
216+
217+
var possibleFrameworks = new List<String>();
218+
219+
if (context.Environment.Runtime.IsCoreClr)
220+
{
221+
possibleFrameworks.Add("netcoreapp");
222+
possibleFrameworks.Add("net4"); // TODO: Remove this after debugging
223+
}
224+
else
225+
{
226+
possibleFrameworks.Add("net4");
227+
}
228+
possibleFrameworks.Add("netstandard");
229+
230+
foreach (var framework in possibleFrameworks)
231+
{
232+
dllPath = context.Tools.Resolve(string.Format(pathFormat, packageName, packageVersion, framework, assemblyName));
233+
if (dllPath is null)
234+
dllPath = context.Tools.Resolve(string.Format(pathFormat, packageName, "*", framework, assemblyName));
235+
if (dllPath is object)
236+
break;
237+
}
238+
239+
return dllPath;
240+
}
241+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#load ./AddinData.cake

0 commit comments

Comments
 (0)