Skip to content

Commit

Permalink
Add Base and Inherit target definition methods
Browse files Browse the repository at this point in the history
  • Loading branch information
matkoch committed May 27, 2020
1 parent dc1f5a0 commit a8016f1
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,38 @@ public void TestMultipleInheritance()
b.AllDependencies.Should().NotBeEmpty();
}

[Fact]
public void TestRequirementValidation()
{
EnvironmentInfo.SetVariable("StringParameter", "hello");
var build = new ParameterBuild();
var targets = ExecutableTargetFactory.CreateAll(build, x => ((IParameterInterface)x).HelloWorld);

// must not throw
RequirementService.ValidateRequirements(build, targets);
}

[Fact]
public void TestInvalidDependencyType()
{
var build = new InvalidDependencyTypeTestBuild();
Assert.Throws<InvalidCastException>(() => ExecutableTargetFactory.CreateAll(build, x => x.E));
}

private interface IParameterInterface
{
[Parameter] string StringParameter => InjectionUtility.GetInjectionValue(() => StringParameter);

public Target HelloWorld => _ => _
.Requires(() => StringParameter)
.Executes(() =>
{
Logger.Info(StringParameter);
});
}

private class ParameterBuild : NukeBuild, IParameterInterface { }

private class TestBuild : NukeBuild, ITestBuild
{
public Target E => _ => _
Expand Down
52 changes: 52 additions & 0 deletions source/Nuke.Common.Tests/Execution/ExecutableTargetFactoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,57 @@ private class TestBuild : NukeBuild
.After(B)
.Before(C);
}

[Fact]
public void TestInheritance()
{
var build = new TestFinalBuild();
var targets = ExecutableTargetFactory.CreateAll(build);

var shared = targets.Single(x => x.Name == nameof(TestFinalBuild.SharedTarget));
var specific = targets.Single(x => x.Name == nameof(TestFinalBuild.SpecificTarget));

shared.Actions.Should().HaveCount(1);
shared.ExecutionDependencies.Single().Name.Should().Be(nameof(TestFinalBuild.SpecificTarget));
shared.Description.Should().Be(nameof(TestFinalBuild.SharedTarget));

specific.Actions.Should().HaveCount(1);
specific.OrderDependencies.Single().Name.Should().Be(nameof(TestFinalBuild.SharedTarget));
specific.Description.Should().Be(nameof(TestFinalBuild.SpecificTarget));
}

private interface ITestSharedBuild
{
Target SharedTarget => _ => _
.Executes(() => { });
}

private class TestBaseBuild : NukeBuild
{
public virtual Target SpecificTarget => _ => _
.Executes(() => { });
}

private class TestIntermediateBuild : TestBaseBuild, ITestSharedBuild
{
public override Target SpecificTarget => _ => _
.Base()
.After(SharedTarget);

public virtual Target SharedTarget => _ => _
.Inherit<ITestSharedBuild>(x => x.SharedTarget)
.DependsOn(SpecificTarget);
}

private class TestFinalBuild : TestIntermediateBuild
{
public override Target SpecificTarget => _ => _
.Base()
.Description(nameof(SpecificTarget));

public override Target SharedTarget => _ => _
.Base()
.Description(nameof(SharedTarget));
}
}
}
20 changes: 16 additions & 4 deletions source/Nuke.Common/Execution/ExecutableTargetFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using NuGet.Packaging;
using Nuke.Common.Utilities.Collections;

namespace Nuke.Common.Execution
{
Expand All @@ -20,18 +22,28 @@ public static IReadOnlyCollection<ExecutableTarget> CreateAll<T>(
params Expression<Func<T, Target>>[] defaultTargetExpressions)
where T : NukeBuild
{
var defaultTargets = defaultTargetExpressions.Select(x => x.Compile().Invoke(build)).ToList();
var properties = build.GetType()
var buildType = build.GetType();
var defaultProperties = buildType.GetInterfaces()
.SelectMany(x => x.GetProperties(ReflectionService.Instance))
.Where(x => buildType.GetProperty(x.Name) == null);
var properties = buildType
.GetProperties(ReflectionService.Instance) // TODO: static targets?
.Concat(build.GetType().GetInterfaces().SelectMany(x => x.GetProperties(ReflectionService.Instance)))
.Concat(defaultProperties)
.Where(x => x.PropertyType == typeof(Target)).ToList();
var defaultTargets = defaultTargetExpressions.Select(x => x.Compile().Invoke(build)).ToList();

var executables = new List<ExecutableTarget>();

foreach (var property in properties)
{
var baseMembers = buildType
.Descendants(x => x.BaseType)
.Select(x => x.GetProperty(property.Name))
.Where(x => x != null && x.DeclaringType == x.ReflectedType)
.Reverse().ToList();
var definition = new TargetDefinition(build, new Stack<PropertyInfo>(baseMembers));

var factory = (Target) property.GetValue(build);
var definition = new TargetDefinition(build);
factory.Invoke(definition);

var target = new ExecutableTarget
Expand Down
60 changes: 39 additions & 21 deletions source/Nuke.Common/Execution/ReflectionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,45 @@ public static object GetValue(this MemberInfo member, object obj = null, object[
};
}

public static IEnumerable<object> GetArguments(this MethodCallExpression methodCall)
{
return methodCall.Arguments.Cast<ConstantExpression>().Select(x => x.Value);
}

public static TResult GetValueNonVirtual<TResult>(this MemberInfo member, object obj, params object[] arguments)
{
ControlFlow.Assert(member is PropertyInfo || member is MethodInfo, "member is PropertyInfo || member is MethodInfo");
var method = member is PropertyInfo property
? property.GetMethod
: (MethodInfo) member;

var funcType = Expression.GetFuncType(method.GetParameters().Select(x => x.ParameterType)
.Concat(method.ReturnType).ToArray());
var functionPointer = method.NotNull("method != null").MethodHandle.GetFunctionPointer();
var nonVirtualDelegate = (Delegate) Activator.CreateInstance(funcType, obj, functionPointer)
.NotNull("nonVirtualDelegate != null");

return (TResult) nonVirtualDelegate.DynamicInvoke(arguments);

// var method = (MethodInfo) func.GetMemberInfo();
//
// var dynamicMethod = new DynamicMethod(
// name: $"Own{method.Name}",
// returnType: typeof(TResult),
// parameterTypes: new[] { typeof(TObject) }.Concat(method.GetParameters().Select(x => x.ParameterType)).ToArray(),
// owner: typeof(TObject));
//
// var generator = dynamicMethod.GetILGenerator();
// dynamicMethod.GetParameters().ForEach((x, i) => generator.Emit(OpCodes.Ldarg_S, i));
// generator.Emit(OpCodes.Call, method);
// generator.Emit(OpCodes.Ret);
//
// var methodCallExpression = (MethodCallExpression) func.Body;
// var arguments = obj.Concat(methodCallExpression.Arguments.Cast<ConstantExpression>().Select(x => x.Value)).ToArray();
//
// return (TResult) dynamicMethod.Invoke(obj: null, arguments);
}

public static void SetValue(this MemberInfo member, object instance, object value)
{
// TODO: check if member is not (static && readonly)
Expand Down Expand Up @@ -312,26 +351,5 @@ public static T InvokeMember<T>(
{
return (T) type.InvokeMember(memberName, target, bindingFlags, args);
}

public static TResult InvokeNonVirtual<TObject, TResult>(this TObject obj, Expression<Func<TObject, TResult>> func)
{
var method = (MethodInfo) func.GetMemberInfo();

var dynamicMethod = new DynamicMethod(
name: $"Own{method.Name}",
returnType: typeof(TResult),
parameterTypes: new[] { typeof(TObject) }.Concat(method.GetParameters().Select(x => x.ParameterType)).ToArray(),
owner: typeof(TObject));

var generator = dynamicMethod.GetILGenerator();
dynamicMethod.GetParameters().ForEach((x, i) => generator.Emit(OpCodes.Ldarg_S, i));
generator.Emit(OpCodes.Call, method);
generator.Emit(OpCodes.Ret);

var methodCallExpression = (MethodCallExpression) func.Body;
var arguments = obj.Concat(methodCallExpression.Arguments.Cast<ConstantExpression>().Select(x => x.Value)).ToArray();

return (TResult) dynamicMethod.Invoke(obj: null, arguments);
}
}
}
2 changes: 1 addition & 1 deletion source/Nuke.Common/Execution/RequirementService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static void ValidateRequirements(NukeBuild build, IReadOnlyCollection<Exe
private static bool IsMemberNull(MemberInfo member, NukeBuild build, ExecutableTarget target = null)
{
member = member.DeclaringType != build.GetType()
? build.GetType().GetMember(member.Name).Single()
? build.GetType().GetMember(member.Name).SingleOrDefault() ?? member
: member;

var from = target != null ? $"from target '{target.Name}' " : string.Empty;
Expand Down
26 changes: 25 additions & 1 deletion source/Nuke.Common/Execution/TargetDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Nuke.Common.Utilities.Collections;

namespace Nuke.Common.Execution
{
internal class TargetDefinition : ITargetDefinition
{
public TargetDefinition(NukeBuild build)
private readonly Stack<PropertyInfo> _baseMembers;

public TargetDefinition(NukeBuild build, Stack<PropertyInfo> baseMembers)
{
Build = build;
_baseMembers = baseMembers;
}

public NukeBuild Build { get; }
Expand Down Expand Up @@ -179,5 +184,24 @@ public ITargetDefinition Unlisted()
IsInternal = true;
return this;
}

public ITargetDefinition Base()
{
ControlFlow.Assert(_baseMembers.Count > 0, "_baseMembers.Count > 0");
Inherit(_baseMembers.Pop().GetValueNonVirtual<Target>(Build));
return this;
}

public ITargetDefinition Inherit(params Target[] targets)
{
targets.ForEach(x => x.Invoke(this));
return this;
}

public ITargetDefinition Inherit<T>(params Expression<Func<T, Target>>[] targets)
{
Inherit(targets.Select(x => x.GetMemberInfo().GetValueNonVirtual<Target>(Build)).ToArray());
return this;
}
}
}
10 changes: 10 additions & 0 deletions source/Nuke.Common/ITargetDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ ITargetDefinition Requires<T>(params Expression<Func<T?>>[] parameterRequirement
/// Defines that this target should not be listed.
/// </summary>
ITargetDefinition Unlisted();

/// <summary>
/// Inherits base target definition.
/// </summary>
ITargetDefinition Base();

/// <summary>
/// Inherits target definition.
/// </summary>
ITargetDefinition Inherit<T>(params Expression<Func<T, Target>>[] targets);
}

/// <summary>
Expand Down

0 comments on commit a8016f1

Please sign in to comment.