Visual Studio resolve failure #966

Open
opened 2025-02-27 20:19:54 -06:00 by timcassell · 3 comments
timcassell commented 2025-02-27 20:19:54 -06:00 (Migrated from github.com)

It works fine when I run dotnet build on command line, but when I build in Visual Studio 2022, I get this error. I can't figure out why.

Failed to resolve assembly: 'System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
Code

using System;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.Cecil;

namespace BenchmarkDotNet.Weaver;

internal class CustomAssemblyResolver : DefaultAssemblyResolver
{
    public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
        // Fix StackOverflow. https://github.com/jbevain/cecil/issues/573
        => name.Name is "netstandard"
            ? AssemblyDefinition.ReadAssembly(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), Path.ChangeExtension(name.Name, ".dll")), parameters)
            : base.Resolve(name, parameters);
}

/// <summary>
/// The Task used by MSBuild to weave the assembly.
/// </summary>
public sealed class WeaveAssemblyTask : Task
{
    /// <summary>
    /// The directory of the output.
    /// </summary>
    [Required]
    public string TargetDir { get; set; }

    /// <summary>
    /// The path of the target assembly.
    /// </summary>
    [Required]
    public string TargetAssembly { get; set; }

    /// <summary>
    /// Runs the weave assembly task.
    /// </summary>
    /// <returns><see langword="true"/> if successful; <see langword="false"/> otherwise.</returns>
    public override bool Execute()
    {
        if (!File.Exists(TargetAssembly))
        {
            Log.LogError($"Assembly not found: {TargetAssembly}");
            return false;
        }

        var resolver = new CustomAssemblyResolver();
        resolver.AddSearchDirectory(TargetDir);

        // ReaderParameters { ReadWrite = true } is necessary to later write the file.
        // https://stackoverflow.com/questions/41840455/locked-target-assembly-with-mono-cecil-and-pcl-code-injection
        var readerParameters = new ReaderParameters
        {
            ReadWrite = true,
            AssemblyResolver = resolver
        };

        bool benchmarkMethodsImplAdjusted = false;
        try
        {
            using var module = ModuleDefinition.ReadModule(TargetAssembly, readerParameters);

            foreach (var type in module.Types)
            {
                ProcessType(type, ref benchmarkMethodsImplAdjusted);
            }

            // Write the modified assembly to file.
            module.Write();
        }
        catch (Exception e)
        {
            if (benchmarkMethodsImplAdjusted)
            {
                Log.LogWarning($"Benchmark methods were found that require NoInlining, and assembly weaving failed.{Environment.NewLine}{e}");
            }
        }
        return true;
    }

    private static void ProcessType(TypeDefinition type, ref bool benchmarkMethodsImplAdjusted)
    {
        // We can skip non-public types as they are not valid for benchmarks.
        if (type.IsNotPublic)
        {
            return;
        }

        // Remove AggressiveInlining and add NoInlining to all [Benchmark] methods.
        foreach (var method in type.Methods)
        {
            if (method.CustomAttributes.Any(IsBenchmarkAttribute))
            {
                var oldImpl = method.ImplAttributes;
                method.ImplAttributes = (oldImpl & ~MethodImplAttributes.AggressiveInlining) | MethodImplAttributes.NoInlining;
                benchmarkMethodsImplAdjusted |= (oldImpl & MethodImplAttributes.NoInlining) == 0;
            }
        }

        // Recursively process nested types
        foreach (var nestedType in type.NestedTypes)
        {
            ProcessType(nestedType, ref benchmarkMethodsImplAdjusted);
        }
    }

    private static bool IsBenchmarkAttribute(CustomAttribute attribute)
    {
        // BenchmarkAttribute is unsealed, so we need to walk its hierarchy.
        for (var attr = attribute.AttributeType; attr != null; attr = attr.Resolve()?.BaseType)
        {
            if (attr.FullName == "BenchmarkDotNet.Attributes.BenchmarkAttribute")
            {
                return true;
            }
        }
        return false;
    }
}

If I add a check for "System.Runtime" in the custom assembly resolver to do the same thing as "netstandard", and whether or not I include "mscorlib", I still get an error only in VS.

Failed to resolve assembly: 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
It works fine when I run `dotnet build` on command line, but when I build in Visual Studio 2022, I get this error. I can't figure out why. ```cmd Failed to resolve assembly: 'System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' ``` <details><summary>Code</summary> <p> ```cs using System; using System.IO; using System.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Mono.Cecil; namespace BenchmarkDotNet.Weaver; internal class CustomAssemblyResolver : DefaultAssemblyResolver { public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) // Fix StackOverflow. https://github.com/jbevain/cecil/issues/573 => name.Name is "netstandard" ? AssemblyDefinition.ReadAssembly(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), Path.ChangeExtension(name.Name, ".dll")), parameters) : base.Resolve(name, parameters); } /// <summary> /// The Task used by MSBuild to weave the assembly. /// </summary> public sealed class WeaveAssemblyTask : Task { /// <summary> /// The directory of the output. /// </summary> [Required] public string TargetDir { get; set; } /// <summary> /// The path of the target assembly. /// </summary> [Required] public string TargetAssembly { get; set; } /// <summary> /// Runs the weave assembly task. /// </summary> /// <returns><see langword="true"/> if successful; <see langword="false"/> otherwise.</returns> public override bool Execute() { if (!File.Exists(TargetAssembly)) { Log.LogError($"Assembly not found: {TargetAssembly}"); return false; } var resolver = new CustomAssemblyResolver(); resolver.AddSearchDirectory(TargetDir); // ReaderParameters { ReadWrite = true } is necessary to later write the file. // https://stackoverflow.com/questions/41840455/locked-target-assembly-with-mono-cecil-and-pcl-code-injection var readerParameters = new ReaderParameters { ReadWrite = true, AssemblyResolver = resolver }; bool benchmarkMethodsImplAdjusted = false; try { using var module = ModuleDefinition.ReadModule(TargetAssembly, readerParameters); foreach (var type in module.Types) { ProcessType(type, ref benchmarkMethodsImplAdjusted); } // Write the modified assembly to file. module.Write(); } catch (Exception e) { if (benchmarkMethodsImplAdjusted) { Log.LogWarning($"Benchmark methods were found that require NoInlining, and assembly weaving failed.{Environment.NewLine}{e}"); } } return true; } private static void ProcessType(TypeDefinition type, ref bool benchmarkMethodsImplAdjusted) { // We can skip non-public types as they are not valid for benchmarks. if (type.IsNotPublic) { return; } // Remove AggressiveInlining and add NoInlining to all [Benchmark] methods. foreach (var method in type.Methods) { if (method.CustomAttributes.Any(IsBenchmarkAttribute)) { var oldImpl = method.ImplAttributes; method.ImplAttributes = (oldImpl & ~MethodImplAttributes.AggressiveInlining) | MethodImplAttributes.NoInlining; benchmarkMethodsImplAdjusted |= (oldImpl & MethodImplAttributes.NoInlining) == 0; } } // Recursively process nested types foreach (var nestedType in type.NestedTypes) { ProcessType(nestedType, ref benchmarkMethodsImplAdjusted); } } private static bool IsBenchmarkAttribute(CustomAttribute attribute) { // BenchmarkAttribute is unsealed, so we need to walk its hierarchy. for (var attr = attribute.AttributeType; attr != null; attr = attr.Resolve()?.BaseType) { if (attr.FullName == "BenchmarkDotNet.Attributes.BenchmarkAttribute") { return true; } } return false; } } ``` </p> </details> If I add a check for "System.Runtime" in the custom assembly resolver to do the same thing as "netstandard", and whether or not I include "mscorlib", I still get an error only in VS. ```cmd Failed to resolve assembly: 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' ```
timcassell commented 2025-03-04 02:38:59 -06:00 (Migrated from github.com)

I don't actually care about any types from runtime assemblies, so my fix was to just do this.

public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
    => name.Name is "netstandard" or "mscorlib" or "System.Runtime" or "System.Private.CoreLib"
        ? null
        : base.Resolve(name, parameters);

Though it's obviously not a general solution.

I don't actually care about any types from runtime assemblies, so my fix was to just do this. ```cs public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) => name.Name is "netstandard" or "mscorlib" or "System.Runtime" or "System.Private.CoreLib" ? null : base.Resolve(name, parameters); ``` Though it's obviously not a general solution.
timcassell commented 2025-06-04 15:35:03 -05:00 (Migrated from github.com)

Skipping resolving runtime assemblies ended up causing errors when a type from a runtime assembly is used in the target assembly. Any other workarounds or fixes?

Skipping resolving runtime assemblies ended up causing errors when a type from a runtime assembly is used in the target assembly. Any other workarounds or fixes?
Zastai commented 2026-05-03 05:16:02 -05:00 (Migrated from github.com)

Are you using typeof() anywhere to refer to types you are working with? Because that may cause references to your build context's runtime.

Unless that's changed in VS2026, Visual Studio runs msbuild (.NET Framework), not dotnet build.

So if you have code that creates type references based on actual types in your code, you are likely to create references to, say, System.Int32 in System.Runtime, but then running that IL under .NET framework will fail (because it would need a reference to System.Int32 in mscorlib.

Are you using `typeof()` anywhere to refer to types you are working with? Because that may cause references to your build context's runtime. Unless that's changed in VS2026, Visual Studio runs `msbuild` (.NET Framework), not `dotnet build`. So if you have code that creates type references based on actual types in your code, you are likely to create references to, say, `System.Int32` in `System.Runtime`, but then running that IL under .NET framework will fail (because it would need a reference to `System.Int32` in mscorlib.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-jbevain/cecil#966
No description provided.