Skip to content

Commit e2ae904

Browse files
RoslynCodeTaskFactory: Log MSB3753 when task class does not implement ITask (#13517)
## Summary `RoslynCodeTaskFactory.CreateTask()` silently returned `null` when the inline task class did not implement `ITask`, causing the engine to emit the generic MSB4060 error. `CodeTaskFactory` already checks for this and emits the more specific MSB3753 (`CodeTaskFactory.NeedsITaskInterface`). ## Changes - **`RoslynCodeTaskFactory.cs`**: Added null check after `Activator.CreateInstance(TaskType) as ITask` — logs MSB3753 via the existing `_log` instance (set during `Initialize`), matching `CodeTaskFactory` behavior. - **`RoslynCodeTaskFactory_Tests.cs`**: Added `ClassDoesNotInheritFromITask` test covering both in-proc and out-of-proc (`MSBUILDFORCEINLINETASKFACTORIESOUTOFPROC`) paths. ## Context I noticed this when trying to migrate usage of CodeTaskFactory to RoslynCodeTaskFactory and checking for coverage parity between these two factories as part of that. The `CodeTaskFactory` equivalent test (`CodeTaskFactoryTests.cs`) already validates this behavior. This was an oversight in `RoslynCodeTaskFactory` — the two factories should produce the same diagnostic for this user error.
1 parent d1f8fe6 commit e2ae904

2 files changed

Lines changed: 60 additions & 2 deletions

File tree

src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using VerifyTests;
1919
using VerifyXunit;
2020
using Xunit;
21+
using Xunit.Abstractions;
2122

2223
using static VerifyXunit.Verifier;
2324

@@ -32,8 +33,11 @@ public class RoslynCodeTaskFactory_Tests
3233

3334
private readonly VerifySettings _verifySettings;
3435

35-
public RoslynCodeTaskFactory_Tests()
36+
private readonly ITestOutputHelper _testOutput;
37+
38+
public RoslynCodeTaskFactory_Tests(ITestOutputHelper testOutput)
3639
{
40+
_testOutput = testOutput;
3741
UseProjectRelativeDirectory("TaskFactorySource");
3842

3943
_verifySettings = new();
@@ -784,6 +788,49 @@ public override bool Execute()
784788
}
785789
}
786790

791+
[Theory]
792+
[InlineData(false)]
793+
[InlineData(true)]
794+
public void ClassDoesNotInheritFromITask(bool forceOutOfProc)
795+
{
796+
const string taskName = "ClassDoesNotInheritFromITask";
797+
string unformattedMessage = ResourceUtilities.GetResourceString("CodeTaskFactory.NeedsITaskInterface");
798+
799+
string projectContent = $$"""
800+
<Project>
801+
<UsingTask TaskName="{{taskName}}" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
802+
<Task>
803+
<Code Type="Class">
804+
namespace InlineTask
805+
{
806+
public class {{taskName}}
807+
{
808+
public bool Execute()
809+
{
810+
return true;
811+
}
812+
}
813+
}
814+
</Code>
815+
</Task>
816+
</UsingTask>
817+
<Target Name="Build">
818+
<{{taskName}} />
819+
</Target>
820+
</Project>
821+
""";
822+
823+
using TestEnvironment env = TestEnvironment.Create(_testOutput);
824+
if (forceOutOfProc)
825+
{
826+
env.SetEnvironmentVariable("MSBUILDFORCEINLINETASKFACTORIESOUTOFPROC", "1");
827+
}
828+
829+
TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles(projectContent);
830+
MockLogger logger = proj.BuildProjectExpectFailure();
831+
logger.AssertLogContains(unformattedMessage);
832+
}
833+
787834
[Fact]
788835
public void EmbedsGeneratedFromSourceFileInBinlog()
789836
{

src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,18 @@ public ITask CreateTask(IBuildEngine taskFactoryLoggingHost)
141141
{
142142
// The type of the task has already been determined and the assembly is already loaded after compilation so
143143
// just create an instance of the type and return it.
144-
return Activator.CreateInstance(TaskType) as ITask;
144+
ITask taskInstance = Activator.CreateInstance(TaskType) as ITask;
145+
if (taskInstance is null)
146+
{
147+
TaskLoggingHelper taskInvocationLog = new TaskLoggingHelper(taskFactoryLoggingHost, _taskName)
148+
{
149+
TaskResources = AssemblyResources.PrimaryResources,
150+
HelpKeywordPrefix = "MSBuild."
151+
};
152+
taskInvocationLog.LogErrorWithCodeFromResources("CodeTaskFactory.NeedsITaskInterface", _taskName);
153+
}
154+
155+
return taskInstance;
145156
}
146157

147158
/// <inheritdoc cref="ITaskFactory.GetTaskParameters"/>

0 commit comments

Comments
 (0)