模仿 msbuild 进程的程序集解析

Posted

技术标签:

【中文标题】模仿 msbuild 进程的程序集解析【英文标题】:Mimicking assembly resolution of the msbuild process 【发布时间】:2009-06-26 19:02:20 【问题描述】:

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

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

您将如何模仿(或直接使用)msbuild 的程序集解析过程?

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

【问题讨论】:

【参考方案1】:

我今天遇到了这个问题,我发现了这篇关于如何解决的旧博客文章:

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

【讨论】:

【参考方案2】:

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

以下是当您针对框架版本 2 时演示违规的示例代码:

using System.Runtime;

class Program

    static void Main()
    
        GCSettings.LatencyMode = GCLatencyMode.LowLatency;
    

【讨论】:

【参考方案3】:

这应该告诉你如何做你真正想做的事,但我认为你应该使用我提供的 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:\t0\r\nFileVersion:\t1", item.FullName, assemblyVersion);

        
        Console.WriteLine("\r\nPress any key to continue");
        Console.ReadKey();
    

【讨论】:

【参考方案4】:

为什么不直接针对您的项目或解决方案文件调用 msbuild,将 /v:d 扩展名传递给它,然后解析输出文件以获得所需的信息?例如,对于每个程序集分辨率,您会看到如下内容:

 主要参考“System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089”。
      解析的文件路径为“c:\WINNT\Microsoft.NET\Framework\v2.0.50727\System.Data.dll”。
      在搜索路径位置“TargetFrameworkDirectory”找到参考。
          对于 SearchPath“TargetFrameworkDirectory”。
          考虑“C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.exe”,但它不存在。
          考虑“C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.dll”,但它不存在。
          考虑“C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.Data.exe”,但它不存在。
          考虑“C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.Data.dll”,但它不存在。
          考虑“c:\WINNT\Microsoft.NET\Framework\v3.5\System.Data.exe”,但它不存在。
          考虑“c:\WINNT\Microsoft.NET\Framework\v3.5\System.Data.dll”,但它不存在。
          考虑“c:\WINNT\Microsoft.NET\Framework\v3.0\System.Data.exe”,但它不存在。
          考虑“c:\WINNT\Microsoft.NET\Framework\v3.0\System.Data.dll”,但它不存在。
          考虑“c:\WINNT\Microsoft.NET\Framework\v2.0.50727\System.Data.exe”,但它不存在。
      此参考不是“CopyLocal”,因为它是必备文件。

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

【讨论】:

【参考方案5】:

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

Microsoft.Build.Shared.TypeLoader

有一个方法叫

internal LoadedType Load(string typeName, AssemblyLoadInfo assembly);

这可能有帮助?

不管怎样,有了反射器,你可以得到代码,并希望直接重用系统。

【讨论】:

【参考方案6】:

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

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

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

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

【讨论】:

我对编译时程序集的来源很感兴趣。在运行时,我想查看 XML 以查找其 Version 属性为“System.Xml”的引用,并找到 msbuild 找到的相同程序集。 感谢您尝试回答。不幸的是,这些都不是运行时解决方案。 你到底想做什么?如果我理解的话,我可能会做到。您是否尝试解析系统程序集的 .csproj,确定它们的路径,然后获取程序集版本?您是否尝试对非系统程序集执行此操作?所有系统程序集都将在运行时从全局程序集缓存加载,但在编译时从不同的位置加载,因此这可能是一个重要的区别。 是的。我想解析一个 .csproj 文件,找到汇编文件并获取它们的版本。我现在只关心系统组件,但将来可能会有其他的。 更详细一点:我有一组针对 csproj 文件运行的验证例程。我想添加一个检查引用程序集的版本。由于与保持我们的选项保持开放有关的原因(我不一定同意),我们决定即使我们的项目将针对 .NET 框架版本 3.5,我们的程序集引用都不应指向高于版本 3.3 的文件(还)。【参考方案7】:

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

【讨论】:

以上是关于模仿 msbuild 进程的程序集解析的主要内容,如果未能解决你的问题,请参考以下文章

dotnet 通过引用 msbuild 程序集实现自己定制编译器

自定义 MsBuild 任务因程序集加载错误而失败

使用 msbuild (CLI) 编译时程序集引用损坏

MSBuild:如何从 AssmeblyInfo.cs 中读取程序集版本和文件版本?

msbuild 使用 ProduceOnlyReferenceAssembly 创建作为引用的仅公开成员程序集

在 MSBuild、Team Build 和 TFS 中使用 PFX 文件对程序集进行签名