views:

574

answers:

7

I am writing a validation tool that checks the versions of files referenced in a project. I want to use the same resolution process that MSBuild uses.

For example, Assembly.Load(..) requires a fully-qualified assembly name. However, in the project file, we may only have something like "System.Xml". MSBuild probably uses the project's target framework version and some other heuristics to decide which version of System.Xml to load.

How would you go about mimicking (or directly using) msbuild's assembly resolution process?

In other words, at run-time, I want to take the string "System.Xml", along with other info found in a .csproj file and find the same file that msbuild would find.

A: 

To directly mimic the CLR resolution process you could write a custom MSBuild task although I don't see what it would achieve.

MSBuild doesn't resolve assemblies. They are resolved by the CLR. This article describes how the runtime resolves assemblies: http://msdn.microsoft.com/en-us/library/yx7xezcf.aspx

When you are in Visual Studio the System assemblies come from the filesystem, but when they are loaded at runtime they come from the GAC. http://p3net.mvps.org/Topics/Basics/IntegratingGACWithVS.aspx

If you still have questions please clarify.

David Silva Smith
I am interested in where assemblies come from at compile time. At run-time, I want to look at the XML for a reference whose Version attribute is "System.Xml" and find the same assembly that msbuild finds.
Neil Whitaker
Thank you for trying to answer. Unfortunately, neither of those are run-time solutions.
Neil Whitaker
What exactly are you trying to do? I can probably do it if I understood. Are you trying to parse a .csproj for system assemblies, determine their paths, and then get the assembly version? Are you trying to do this with non-system assemblies? All the System assemblies will be loaded from the Global Assembly Cache at runtime, but from a different location at compile time so this could be an important distinction.
David Silva Smith
Yes. I want to parse a .csproj file, locate the assembly files, and get their versions. I am only concerned with system assemblies right now, but others might be useful in the future.
Neil Whitaker
A little more detail:I have a set of validation routines that I run against csproj files. I want to add one that checks the versions of referenced assemblies.For reasons having to do with keeping our options open (which I don't necessarily agree with), it was decided that even though our projects will target the .NET framework version 3.5, none of our assembly references should be to files above version 3.3 (yet).
Neil Whitaker
+1  A: 

If you target the Framework version you want to be compatible with instead of targeting 3.5, Visual Studio 2008 SP1 and FxCop 1.36 RTM added rule CA 1903: Use only API from targeted framework to ensure you stay compatible with the target framework version. Turning that rule on and treating it as an error will fail your Build and provide the behavior you want.

Here is sample code demonstrating a violation when you are targeting framework version 2:

using System.Runtime;

class Program
{
    static void Main()
    {
        GCSettings.LatencyMode = GCLatencyMode.LowLatency;
    }
}
David Silva Smith
A: 

You can view assembly versions as they are loaded at runtime using this method. you could parse the assembly version and throw if it is greater than what you are expecting. I realize this isn't doing it at compile time, but thought it might work for your situation.

class Program
{
    static void Main()
    {
        AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
        Assembly.LoadWithPartialName("System.AddIn");
        Console.ReadKey();
    }

    static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
    {
        var assemblyVersion = ((AssemblyFileVersionAttribute)args.LoadedAssembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), true)[0]).Version.ToString();
        Console.WriteLine("Fullname:\t{0}\r\nFileVersion:\t{1}",args.LoadedAssembly.FullName, assemblyVersion);
    }
}
David Silva Smith
As it was stated before, David wants to know compile-time rather then runtime assembly resolution behavior, and they're absolutely different.
Alex Yakunin
A: 

This should show you how to do what you really want, but I think you should use the FXCop answer I provided.

static void Main()
    {
        string targetFile = @"test.csproj";
        XDocument xmlDoc = XDocument.Load(targetFile);
        XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";

        var references = from reference in xmlDoc.Descendants(ns + "ItemGroup").Descendants(ns + "Reference")
                         select reference.Attribute("Include").Value;

        foreach (var reference in references)
        {
            Assembly.LoadWithPartialName(reference);
        }

        foreach (var item in AppDomain.CurrentDomain.GetAssemblies())
        {
            var assemblyVersion = ((AssemblyFileVersionAttribute)item.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), true)[0]).Version.ToString();
            Console.WriteLine("\r\nFullname:\t{0}\r\nFileVersion:\t{1}", item.FullName, assemblyVersion);

        }
        Console.WriteLine("\r\nPress any key to continue");
        Console.ReadKey();
    }
David Silva Smith
A: 

This might help: Resolving Binary References in MSBuild

Alex Yakunin
A: 

If you get yourself a free copy of Reflector, you can examine the internals of the MSBuild.exe file itself. I notice there is a class

Microsoft.Build.Shared.TypeLoader

that has a method called

internal LoadedType Load(string typeName, AssemblyLoadInfo assembly);

which may help?

Anyway, with reflector you can get the code, and hopefully reuse the system directly.

Steve Cooper
+1  A: 

Why not just call msbuild against your project or solution file, pass it the /v:d extension, and parse the output file for the information you want? For instance, you'll see something like the following for each assembly resolution:

  Primary reference "System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089".
      Resolved file path is "c:\WINNT\Microsoft.NET\Framework\v2.0.50727\System.Data.dll".
      Reference found at search path location "{TargetFrameworkDirectory}".
          For SearchPath "{TargetFrameworkDirectory}".
          Considered "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.exe", but it didn't exist.
          Considered "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.dll", but it didn't exist.
          Considered "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.Data.exe", but it didn't exist.
          Considered "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.Data.dll", but it didn't exist.
          Considered "c:\WINNT\Microsoft.NET\Framework\v3.5\System.Data.exe", but it didn't exist.
          Considered "c:\WINNT\Microsoft.NET\Framework\v3.5\System.Data.dll", but it didn't exist.
          Considered "c:\WINNT\Microsoft.NET\Framework\v3.0\System.Data.exe", but it didn't exist.
          Considered "c:\WINNT\Microsoft.NET\Framework\v3.0\System.Data.dll", but it didn't exist.
          Considered "c:\WINNT\Microsoft.NET\Framework\v2.0.50727\System.Data.exe", but it didn't exist.
      This reference is not "CopyLocal" because it's a prerequisite file.

Alternatively, MSBuild delegates the task of resolving assemblies to the Microsoft.Build.Tasks.ResolveAssemblyReference class from the Microsoft.Build.Tasks.v3.5 assembly (in my case, building against the 3.5 framework). You can parse the project file and supply an instance of ResolveAssemblyReference with the appropriate (meta)data, and let it perform the resolution for you - seems perfect, since that's exactly what MSBuild does.

Dathan