模仿msbuild进程的程序集解析

时间:2009-06-26 19:02:21

标签: c# msbuild assembly-resolution

我正在编写一个验证工具,用于检查项目中引用的文件版本。我想使用MSBuild使用的相同解析过程。

例如,Assembly.Load(..)需要一个完全限定的程序集名称。但是,在项目文件中,我们可能只有“System.Xml”之类的东西。 MSBuild可能使用项目的目标框架版本和其他一些启发式方法来决定要加载哪个版本的System.Xml。

你将如何模仿(或直接使用)msbuild的装配解决过程?

换句话说,在运行时,我想获取字符串“System.Xml”以及.csproj文件中的其他信息,并找到msbuild会找到的相同文件。

7 个答案:

答案 0 :(得分:3)

我今天遇到了这个问题,我发现了这篇关于如何做的老博文:

http://blogs.msdn.com/b/jomo_fisher/archive/2008/05/22/programmatically-resolve-assembly-name-to-full-path-the-same-way-msbuild-does.aspx

我试了一下,效果很好!我修改了代码以尽可能找到4.5.1版本的程序集,这就是我现在所拥有的:

#if INTERACTIVE
#r "Microsoft.Build.Engine" 
#r "Microsoft.Build.Framework"
#r "Microsoft.Build.Tasks.v4.0"
#r "Microsoft.Build.Utilities.v4.0"
#endif

open System
open System.Reflection
open Microsoft.Build.Tasks
open Microsoft.Build.Utilities
open Microsoft.Build.Framework
open Microsoft.Build.BuildEngine

/// Reference resolution results. All paths are fully qualified.
type ResolutionResults = {
    referencePaths:string array
    referenceDependencyPaths:string array
    relatedPaths:string array
    referenceSatellitePaths:string array
    referenceScatterPaths:string array
    referenceCopyLocalPaths:string array
    suggestedBindingRedirects:string array
    }


let resolve (references:string array, outputDirectory:string) =
    let x = { new IBuildEngine with
                member be.BuildProjectFile(projectFileName, targetNames, globalProperties, targetOutputs) = true
                member be.LogCustomEvent(e) = ()
                member be.LogErrorEvent(e) = ()
                member be.LogMessageEvent(e) = ()
                member be.LogWarningEvent(e) = ()
                member be.ColumnNumberOfTaskNode with get() = 1
                member be.ContinueOnError with get() = true
                member be.LineNumberOfTaskNode with get() = 1
                member be.ProjectFileOfTaskNode with get() = "" }

    let rar = new ResolveAssemblyReference()
    rar.BuildEngine <- x
    rar.IgnoreVersionForFrameworkReferences <- true
    rar.TargetFrameworkVersion <- "v4.5.1"
    rar.TargetedRuntimeVersion <- "v4.5.1"
    rar.TargetFrameworkDirectories <- [||] //[|@"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\"|]
    rar.Assemblies <- [|for r in references -> new Microsoft.Build.Utilities.TaskItem(r) :> ITaskItem|]
    rar.AutoUnify <- true
    rar.SearchPaths <- [| "{CandidateAssemblyFiles}"
                          "{HintPathFromItem}"
                          "{TargetFrameworkDirectory}"
                         // "{Registry:Software\Microsoft\.NetFramework,v3.5,AssemblyFoldersEx}"
                          "{AssemblyFolders}"
                          "{GAC}"
                          "{RawFileName}"
                          outputDirectory |]

    rar.AllowedAssemblyExtensions <- [| ".exe"; ".dll" |]
    rar.TargetProcessorArchitecture <- "x86"
    if not (rar.Execute()) then
        failwith "Could not resolve"
    {
        referencePaths = [| for p in rar.ResolvedFiles -> p.ItemSpec |]
        referenceDependencyPaths = [| for p in rar.ResolvedDependencyFiles -> p.ItemSpec |]
        relatedPaths = [| for p in rar.RelatedFiles -> p.ItemSpec |]
        referenceSatellitePaths = [| for p in rar.SatelliteFiles -> p.ItemSpec |]
        referenceScatterPaths = [| for p in rar.ScatterFiles -> p.ItemSpec |]
        referenceCopyLocalPaths = [| for p in rar.CopyLocalFiles -> p.ItemSpec |]
        suggestedBindingRedirects = [| for p in rar.SuggestedRedirects -> p.ItemSpec |]
    }



[<EntryPoint>]
let main argv = 
    try
      let s = resolve([| "System"
                         "System.Data"
                         "System.Core, Version=4.0.0.0"
                         "Microsoft.SqlServer.Replication" |], "")
      printfn "%A" s.referencePaths
    finally
      ignore (System.Console.ReadKey())

    0

答案 1 :(得分:2)

如果您定位要与之兼容的Framework版本而不是目标3.5,则Visual Studio 2008 SP1和FxCop 1.36 RTM添加了规则CA 1903: Use only API from targeted framework,以确保您与目标框架版本保持兼容。将该规则作为错误处理并将其视为错误将导致您的Build失败并提供您想要的行为。

以下是展示框架版本2的违规示例代码:

using System.Runtime;

class Program
{
    static void Main()
    {
        GCSettings.LatencyMode = GCLatencyMode.LowLatency;
    }
}

答案 2 :(得分:2)

这应该告诉你如何做你真正想要的,但我认为你应该使用我提供的FXCop答案。

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();
    }

答案 3 :(得分:2)

为什么不直接针对项目或解决方案文件调用msbuild,将其传递给/ v:d扩展名,并解析输出文件以获取所需信息?例如,对于每个装配分辨率,您将看到类似下面的内容:

  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.

或者,MSBuild将解决程序集的任务委派给Microsoft.Build.Tasks.v3.5程序集中的Microsoft.Build.Tasks.ResolveAssemblyReference类(在我的情况下,根据3.5框架构建)。您可以解析项目文件并使用适当的(元)数据提供ResolveAssemblyReference的实例,并让它为您执行解析 - 看起来很完美,因为这正是MSBuild所做的。

答案 4 :(得分:1)

如果您获得Reflector的免费副本,则可以检查MSBuild.exe文件本身的内部。我注意到有一个班级

Microsoft.Build.Shared.TypeLoader

有一个名为

的方法
internal LoadedType Load(string typeName, AssemblyLoadInfo assembly);

哪有帮助?

无论如何,使用反射器,您可以获得代码,并希望直接重用该系统。

答案 5 :(得分:0)

要直接模仿CLR解析过程,您可以编写自定义MSBuild任务,尽管我看不到它会实现什么。

MSBuild无法解析程序集。它们由CLR解决。本文介绍运行时如何解析程序集:http://msdn.microsoft.com/en-us/library/yx7xezcf.aspx

当你在Visual Studio中时,系统程序集来自文件系统,但是当它们在运行时加载时,它们来自GAC。 http://p3net.mvps.org/Topics/Basics/IntegratingGACWithVS.aspx

如果您仍有疑问,请澄清。

答案 6 :(得分:0)

这可能会有所帮助:Resolving Binary References in MSBuild