msbuild icon indicating copy to clipboard operation
msbuild copied to clipboard

Bad error when CodeTaskFactory has a mismatch between TaskName and class name

Open Mal-Ayers opened this issue 3 years ago • 3 comments

Issue Description

This was an experiment to use the inline task and in-project C# files (net6.0) to process and provide property information during the build process.

With an inline task, and using the RoslynCodeTaskFactory, the task's file-based source code (a class) appears to cause an error on build using Visual Studio 2022 (Debug--AnyCPU).

Error: https://docs.microsoft.com/en-us/visualstudio/msbuild/errors/msb4175

The task factory "RoslynCodeTaskFactory" could not be loaded from the assembly "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\Microsoft.Build.Tasks.Core.dll". The task factory must return a value for the "TaskType" property.

No other error messages are provided in VS or build output.

There are other ways to process and provide property information (like version details), however, this was only intended as a experiment to verify the capabilities and features discussed in the msbuild docs.

Steps to Reproduce

Using the generated console project (net6.0) with two files--the Program.cs and BuildInformation.cs.

Also added project references Microsoft.Build.Framework and Microsoft.Build.Utilities.Core from NuGet.

There is only the one console project in the test solution.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
</PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Build.Framework" Version="17.2.0" CopyLocal="false" Publish="false" ExcludeAssets="runtime" />
    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.2.0" CopyLocal="false" Publish="false" ExcludeAssets="runtime" />
  </ItemGroup>

  <!-- inline task. -->
  <UsingTask
    TaskName="GetCalculatedVersion"
    TaskFactory="RoslynCodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup>
      <BuildVersion ParameterType="System.String" Output="true" />
    </ParameterGroup>
    <Task>
      <Using Namespace="MyInlineCode" />
      <Code Type="Class" Source="BuildInformation.cs" Language="cs"></Code>
    </Task>
  </UsingTask>

  <Target Name="GetBuildVersion" BeforeTargets="BeforeBuild">
    <Message Importance="high" Text="Running Target GetBuildVersion..." />
    <GetCalculatedVersion>
      <Output TaskParameter="BuildVersion" PropertyName="MyNewVersion" />
    </GetCalculatedVersion>
    <Message Importance="high" Text="... Finished Target GetBuildVersion -- '$(MyNewVersion)'." />
  </Target>
</Project>
// BuildInformation.cs
namespace MyInlineCode
{
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// Represents the project's build information.
    /// </summary>
    public class BuildInformation : Microsoft.Build.Utilities.Task
    {
        #region static properties
        public static readonly Int32 Major = 0;

        public static readonly Int32 Minor = 1;

        public static readonly Int32 Build = (DateTime.Now - new DateTime(2000, 01, 01)).Days;

        public static readonly Int32 Revision = (Int32)((DateTime.Now.TimeOfDay.TotalSeconds) / 2);

        public static readonly Version Version = new(Major, Minor, Build, Revision);
        #endregion static properties

        public override bool Execute()
        {
            // task output
            String BuildVersion = Version.ToString();

            Log.LogMessage(MessageImportance.High, $"Build version set to {BuildVersion}");

            return !Log.HasLoggedErrors;
        }
    }
}
// Program.cs
namespace BuildExperiment
{
   public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("This is a build experiment.");
        }
    }
}

Assessment

This appears to be unrelated to issue #7730, as this source file provides a class and is not a code fragment.

Expected Behavior

The expected behavior is a successful build with the text provided by the inline task and the target displayed via output messages to verify a working experiment.

Versions & Configurations

OS is Windows 10 Pro 21H2 19044.1766; Windows Feature Experience Pack 120.2212.4180.0

and using ...

Microsoft.NET SDK Version: 6.0.302

Microsoft (R) Build Engine version 17.2.1+52cd2da31 for .NET Framework 17.2.1.25201

Microsoft Visual Studio Community 2022 (64-bit) - Current - Version 17.2.6

Vanilla community installation with no special configuration, etc.

Mal-Ayers avatar Jul 21 '22 16:07 Mal-Ayers

@Mal-Ayers I investigated this issue. My findings follow:

  • The error message ('The task factory "RoslynCodeTaskFactory" could not be loaded...') is incorrect and misleading and the real error is not reported.
  • The class name and the task name must match. The UsingTask uses the task name of GetCalculatedVersion to look up the task and fails because the task is named BuildInformation. (That the class and task names must match is noted in the documentation for the Code element.)
  • The UsingTask defines an output parameter and the matching property is not provided on the class.

I modified your code to test.

In the project file, change the task name:

  <UsingTask
    TaskName="BuildInformation"
    <BuildInformation>
      <Output TaskParameter="BuildVersion" PropertyName="MyNewVersion" />
    </BuildInformation>

In BuildInformation.cs, add the output property:

        public string BuildVersion { get; set; }

        public override bool Execute()
        {
            // task output
            BuildVersion = Version.ToString();

There should be a separate issue specifically for the error handling and messaging. (Edit: An issues exists. See #6419.)

jrdodds avatar Jul 28 '22 14:07 jrdodds

  • The error message ('The task factory "RoslynCodeTaskFactory" could not be loaded...') is incorrect and misleading and the real error is not reported.

I just expanded #6419 to cover this case (I agree it's the same root cause with a different task factory).

I'm also changing this bug to be about the suberror The task factory must return a value for the "TaskType" property., which wouldn't be clear even if we correctly said "The CodeTaskFactory failed with error X".

rainersigwald avatar Aug 04 '22 16:08 rainersigwald

@rainersigwald Please assign this issue to me.

jrdodds avatar Aug 04 '22 20:08 jrdodds