Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolve actual Reference path using Microsoft.Build.Evaluation

I'm doing some introspection and analysis of csproj files using the Microsoft.Build.Evaluation tools in a small C# console app. I want to locate the actual location of Reference items, using the same heuristics as MSBuild itself ie the locations described here. I'm heading towards auto conversion of build artifacts into packages, similar to what's outlined on the JetBrains blog here

The only examples I can find expect the HintPath to be correct, for example this project, and I know there are some HintPaths that are not currently correct, I don't want to trust them. This project very close what I'm trying to do, with the added complication that I want to use real resolution behaviour to find dependencies.

I have an instance of a Microsoft.Build.Evaluation.Project object for my csproj, and I can't see any methods available on it that could exersize the resolution for me. I think what I'm hoping for is a magic Resolve() method for a Reference or a ProjectItem, a bit like this method.

I can probably find an alternative by constraining my own search to a set of limited output paths used by this build system, but I'd like to hook into MSBuild if I can.

like image 873
eddie.sholl Avatar asked Aug 01 '15 08:08

eddie.sholl


1 Answers

The reference resolution is one of the trickiest parts of MSBuild. The logic of how assemblies are located is implemented inside the a standard set of tasks: ResolveAssemblyReference, ResolveNativeReference, etc. The logic is how this works is very complicated, you can see that just by looking at number of possible parameters to those tasks.

However you don't need to know the exact logic to find the location of referenced files. There are standard targets called "ResolveAssemblyReferences", "ResolveProjectReferences" and some others more specialized for native references, COM references. Those targets are executed as part of the normal build. If you just execute those targets separately, you can find out the return values, which is exactly what you need. The same mechanism is used by IDE to get location of refereces, for Intellisense, introspection, etc.

Here is how you can do it in code:

using Microsoft.Build.BuildEngine;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using System;
using System.Collections.Generic;

class Program
{
    static int Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: GetReferences.exe <projectFileName>");
            return -1;
        }

        string projectFileName = args[0];

        ConsoleLogger logger = new ConsoleLogger(LoggerVerbosity.Normal);
        BuildManager manager = BuildManager.DefaultBuildManager;

        ProjectInstance projectInstance = new ProjectInstance(projectFileName);
        var result = manager.Build(
            new BuildParameters()
            {
                DetailedSummary = true,
                Loggers = new List<ILogger>() { logger }
            },
            new BuildRequestData(projectInstance, new string[] 
            { 
                "ResolveProjectReferences",
                "ResolveAssemblyReferences"
            }));

        PrintResultItems(result, "ResolveProjectReferences");
        PrintResultItems(result, "ResolveAssemblyReferences");

        return 0;
    }

    private static void PrintResultItems(BuildResult result, string targetName)
    {
        var buildResult = result.ResultsByTarget[targetName];
        var buildResultItems = buildResult.Items;

        if (buildResultItems.Length == 0)
        {
            Console.WriteLine("No refereces detected in target {0}.", targetName);
            return;
        }

        foreach (var item in buildResultItems)
        {
            Console.WriteLine("{0} reference: {1}", targetName, item.ItemSpec);
        }
    }
}

Notice, the engine is called to invoke specific targets in the project. Your project usually does not build, but some targets might be invoked by pre-requisite targets.

Just compile it and will print a sub-set of all dependencies. There might be more dependencies if you use COM references or native dependencies for your project. It should be easy to modify the sample to get those as well.

like image 172
seva titov Avatar answered Nov 16 '22 23:11

seva titov