PVS-Studio中对Visual Studio 2019的支持立即影响了几个不同的组件:IDE插件本身,命令行分析应用程序,C ++和C#分析器以及几个实用程序。 我将简要介绍一下在支持新版本的IDE时遇到的问题以及如何解决这些问题。
在开始之前,我想回顾一下以前对Visual Studio早期版本的支持历史,这将使我们更好地理解任务的愿景以及在某些情况下做出的决定。
从第一个版本的PVS-Studio分析器开始,其中出现了Visual Studio环境的插件(当时它也是Visual Studio 2005的一个版本),支持Visual Studio的新版本对我们来说是一个相当简单的任务-它基本上归结为更新插件项目文件和各种Visual Studio扩展API的依赖关系。 有时有必要额外支持Visual C ++编译器正在逐步学习的C ++语言的新功能,但这通常不会在下一版Visual Studio发行之前立即引起问题。 那时,PVS-Studio中只有一个分析仪-用于C和C ++语言。
对于Visual Studio 2017发行版,一切都发生了变化。除了此版本中此IDE的许多扩展API发生了很大变化之外,在更新之后,我们还遇到了确保当时出现的新C#分析器(以及我们的新C ++层)的工作向后兼容的问题。较旧版本的MSBuild \ Visual Studio的MSBuild项目分析工具)。
因此,在阅读本文之前,我强烈建议您阅读有关Visual Studio 2017支持的相关文章:“
PVS-Studio中的Visual Studio 2017和Roslyn 2.0支持:有时使用现成的解决方案并不像乍看起来那样容易 。” 上面提到的文章描述了我们上次遇到的问题,以及各种组件(例如,PVS-Studio,MSBuild,Roslyn)的交互方案。 阅读本文时,了解这种相互作用将很有帮助。
最终,解决这些问题的方法为我们的分析仪带来了重大变化,并且,正如我们希望的那样,我们随后采用的新方法将有可能在将来更加轻松快捷地支持Visual Studio \ MSBuild的更新版本。 在某种程度上,此假设已通过发布Visual Studio 2017的众多更新得到证实。这种新方法是否对Visual Studio 2019的支持有所帮助? 关于它下面。
适用于Visual Studio 2019的PVS-Studio插件
一切似乎都开始了,还不错。 将插件移植到Visual Studio 2019十分容易,它在此开始并运行良好。 尽管如此,仍立即发现了2个问题,这预示着将来的麻烦。
第一个是
IVsSolutionWorkspaceService接口,用于支持轻量级解决方案加载模式,顺便说一下,在Visual Studio 2017的先前更新之一中已禁用该接口,并使用了
Deprecated属性装饰,该属性只是在组装过程中的警告,但将来会提供更多警告问题。 微软迅速引入了这种模式,并放弃了它。我们非常简单地处理了这个问题-拒绝使用适当的接口。
第二个-当使用插件加载Visual Studio时,出现以下消息:
Visual Studio检测到一个或多个扩展在功能VS更新中存在风险或无法正常运行。查看Visual Studio启动日志(ActivityLog文件)最后加了'i':
警告:扩展名“ PVS-Studio”使用Visual Studio的“同步自动加载”功能。 将来的Visual Studio 2019更新中将不再支持此功能,此时此扩展将不起作用。 请联系扩展供应商以获取更新。对我们来说,这意味着一件事-更改将插件加载到异步模式的方式。 如果我不给您过多的有关与Visual Studio的COM接口进行交互的详细信息,希望您不会感到沮丧,并且我将简要介绍一下这些更改。
Microsoft有一篇有关创建异步加载的插件的文章:“
如何:使用AsyncPackage在后台加载VSPackages ”。 此外,对于每个人来说,很明显,问题不会仅限于这些更改。
主要更改之一是加载或初始化方法。 以前,必需的初始化是通过两种方法进行的-我们的
Package 继承类的被覆盖的
Initialize方法和
OnShellPropertyChange方法。 之所以需要将部分逻辑转移到
OnShellPropertyChange方法,是因为这样的事实:当同步加载插件时,Visual Studio可能尚未完全加载和初始化,因此,在插件初始化阶段无法执行所有必需的操作。 解决此问题的一种方法是等待Visual Studio退出“僵尸”状态并延迟这些操作。 这是逻辑,并已通过检查“僵尸”状态在
OnShellPropertyChange中呈现。
在继承异步加载的插件的抽象
AsyncPackage类中,
Initialize方法具有
密封的修饰符,因此必须在重写的
InitializeAsync方法中完成
初始化 。 我们还必须通过跟踪Visual Studio的“僵尸”状态来更改逻辑,因为我们已停止在插件中接收此信息。 但是,初始化插件后需要执行的许多操作并没有消失。
解决方案是使用
IVsPackageLoadEvents接口的
OnPackageLoaded方法,在该方法中执行需要延迟执行的操作。
从逻辑上讲,由插件的异步加载引起的另一个问题是在启动Visual Studio时缺少PVS-Studio插件命令。 当通过从文件管理器中双击打开分析器日志时(如果需要通过Visual Studio打开它),必要的devenv.exe版本将用命令启动,以打开分析器报告。 启动命令如下所示:
"C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\Common7\IDE\devenv.exe" /command "PVSStudio.OpenAnalysisReport C:\Users\vasiliev\source\repos\ConsoleApp\ConsoleApp.plog"
此处的“ / command”标志用于调用在Visual Studio中注册的命令。 现在这种方法不起作用了,因为在下载插件之前命令不可用。 结果,加载插件后,我不得不停下来分析devenv.exe的启动行,并且没有打开命令行日志的命令的字符串表示形式-实际上,就是在加载日志。 因此,在这种情况下,由于拒绝使用“正确”的界面来处理命令,因此可以通过延迟日志的加载直到插件完全加载来维护必要的功能。
嗯,它似乎已经整理好了,并且一切正常-一切正常加载和打开,没有警告-终于。
然后发生意外情况-Pavel(您好!)安装了一个插件,之后他问为什么我们不进行异步加载?
说我们很惊讶-什么也不说-怎么会这样? 不,真的-这是已安装的插件的新版本,这是该软件包可以同步下载的消息。 我们在机器上使用Alexander(同样向您问好)安装了相同版本的插件-一切正常。 尚不清楚-我们决定查看Visual Studio中加载了哪个版本的PVS-Studio库。 突然发现,尽管Visual Studio 2019的VSIX软件包中包含正确版本的库,但实际上使用了Visual Studio 2017的PVS-Studio库的版本。
修好VSIXInstaller之后,我设法找到了问题的根源-程序包缓存。 当限制对缓存中的程序包的访问权限(C:\ ProgramData \ Microsoft \ VisualStudio \ Packages)时,VSIXInstaller将错误信息写入日志,这一理论也得到了证实。 令人惊讶的是,如果没有错误,则不会将有关从缓存安装软件包的事实的任何信息都不会写入日志。
注意事项 在研究VSIXInstaller和相关库的行为时,他对自己指出,罗斯林和MSBuild具有开源代码,这使它易于阅读,调试和跟踪工作逻辑非常酷。
结果发生了以下情况-安装插件时,VSIXInstaller看到相应的软件包已在缓存中(Visual Studio 2017有一个.vsix软件包),并在安装过程中使用了它而不是实际安装的软件包。 为什么不考虑.vsixmanifest中描述的限制/要求(例如,您可以为其安装扩展名的Visual Studio版本)是一个悬而未决的问题。 因此,事实证明尽管.vsixmanifest包含必要的限制,但为Visual Studio 2017设计的插件已安装在Visual Studio 2019上。
最糟糕的是,这样的安装破坏了Visual Studio的依赖关系图,尽管从外观上看,甚至开发环境似乎运行良好,但实际上一切都很糟糕。 无法安装和卸载扩展,进行更新等。 “恢复”的过程也很不愉快,因为 必须删除扩展名(相应的文件),并手动编辑存储有关已安装软件包信息的配置文件。 一般来说,这不愉快。
为了解决此问题并避免将来出现类似情况,决定为新程序包创建一个GUID,以便准确地将Visual Studio 2017和Visual Studio 2019程序包分开(旧程序包没有此类问题,它们始终使用通用的GUID)。
而且,由于我们谈论的是令人不愉快的惊喜,因此我还要提到一件事-更新为预览版2后,“扩展”标签下的菜单项“已移动”。 看起来还可以,但是访问插件功能的便利性有所降低。 在Visual Studio 2019的后续版本(包括发行版)上,此行为已保留。 在文档或博客中发布该“功能”时,我没有发现任何提及。
现在,似乎一切正常,并且对Visual Studio 2019的插件支持已经完成。 发行了支持Visual Studio 2019的PVS-Studio 7.02之后的第二天,事实并非如此-发现了异步插件的另一个问题。 对于用户来说,这可能是这样的:打开带有分析结果(或开始分析)的窗口时,有时我们的窗口显示为“空”-它不包含任何内容:按钮,带有分析仪警告的表格等。
实际上,在工作过程中有时会重复出现此问题。 但是,它仅在一台机器上重复执行,并且仅在更新“预览版”的第一个版本中的Visual Studio之后才开始出现-有人怀疑在安装/更新过程中发生了故障。 但是,随着时间的流逝,即使在这台机器上,问题也不再重复出现,我们决定将其“自行修复”。 事实证明,没有-太幸运了。 更确切地说,没有运气。
事实证明,问题出在环境窗口本身(
ToolWindowPane类的后代)及其内容(实际上是我们使用网格和按钮的控件)的初始化顺序中。 在某些情况下,控件的初始化发生在窗格初始化之前,尽管所有工作均正常进行,但
FindToolWindowAsync方法(在第一次调用时创建一个窗口)正常工作,但是控件仍然不可见。 我们通过将控件的延迟初始化添加到窗格填充代码中来解决此问题。
支持C#8.0
使用Roslyn作为分析器的基础具有显着优势-无需手动维护新的语言结构。 所有这些都在Microsoft.CodeAnalysis库的框架中得到支持和实现-我们使用现成的结果。 因此,通过更新库来实现对新语法的支持。
当然,就静态分析而言,这里您已经必须自己做所有事情,尤其是处理新的语言构造。 是的,我们通过使用最新版本的Roslyn自动获得新的语法树,但是我们需要教给分析器如何感知和处理树的新/更改节点。
我认为C#8中谈论最多的创新是可为空的引用类型。 在这里我不会写它们-这是一个相当大的话题,值得单独撰写(该文章已经在撰写过程中)。 通常,到目前为止,我们已经决定忽略数据流机制中的可为空的注释(即,我们理解,解析和跳过它们)。 事实是,尽管变量的引用类型为非空,但您仍然可以非常简单地(或错误地)向其写入
null ,这在引用相应链接时可能导致NRE。 在这种情况下,尽管变量的引用类型不可为空,但分析器仍会看到类似的错误,并会警告使用可能为空的引用(当然,如果它在代码中看到这样的分配)。
我想指出,使用可为空的引用类型和随附的语法为编写非常有趣的代码提供了可能。 就我们自己而言,我们将其称为“情感语法”。 下面的代码可以很好地编译:
obj.Calculate(); obj?.Calculate(); obj.Calculate(); obj!?.Calculate(); obj!!!.Calculate();
顺便说一下,在我的工作过程中,我发现了几种使用新语法“填充” Visual Studio的方法。 事实是,当您输入“!”时,不能将字符数限制为一个。 也就是说,您不仅可以编写以下形式的代码:
object temp = null!
而且:
object temp = null!!!;
你可以变态,继续这样写:
object temp = null!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;
该代码成功编译。 但是,如果您从.NET Compiler Platform SDK使用语法可视化工具请求有关语法树的信息,则Visual Studio将崩溃。
您可以从事件查看器中获取有关该问题的信息:
Faulting application name: devenv.exe, version: 16.0.28803.352, time stamp: 0x5cc37012 Faulting module name: WindowsBase.ni.dll, version: 4.8.3745.0, time stamp: 0x5c5bab63 Exception code: 0xc00000fd Fault offset: 0x000c9af4 Faulting process id: 0x3274 Faulting application start time: 0x01d5095e7259362e Faulting application path: C:\Program Files (x86)\ Microsoft Visual Studio\2019\Community\Common7\IDE\devenv.exe Faulting module path: C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\ WindowsBase\4480dfedf0d7b4329838f4bbf953027d\WindowsBase.ni.dll Report Id: 66d41eb2-c658-486d-b417-02961d9c3e4f Faulting package full name: Faulting package-relative application ID:
如果走得更远,并多次增加感叹号的数量,Visual Studio会自行崩溃-不再需要Syntax Visualizer的帮助。 Microsoft.CodeAnalysis库和csc.exe编译器也不会消化此代码。
当然,这些是综合示例,但是对于我来说,这个事实仍然很有趣。
工具集
注意事项 在有关MSBuild项目的对话中,我再次面临翻译“评估”一词的问题。 翻译似乎是最接近的意思,同时听起来很正常,是“建立项目模型”。 如果您有其他翻译选项-可以给我写信,那么阅读会很有趣。
显然,更新工具集将是最耗时的任务。 更准确地说,从一开始就如此,但是现在我倾向于认为最有问题的是插件支持。 特别是,这是由于已经存在的工具集和用于构建MSBuild项目模型的机制,尽管需要扩展,但该机制现在已成功运行。 无需从头开始编写算法,大大简化了任务。 我们在支持Visual Studio 2017的阶段对“我们的”工具集的押注再次得到了证明。
传统上,这一切都始于更新NuGet软件包。 在解决方案的NuGet软件包管理选项卡上,有一个“更新”按钮...,该按钮不起作用。 更新所有软件包时,会出现多个版本冲突,解决所有问题似乎不太正确。 一种更痛苦但又似乎更可靠的方法是“逐段”更新目标Microsoft.Build/Microsoft.CodeAnalysis包。
通过诊断规则的测试,立即发现了差异之一-已经存在的节点的语法树的结构已更改。 没关系,请迅速纠正。
让我提醒您,在工作期间,我们在开源项目上测试分析器(C#,C ++,Java)。 这使您能够很好地测试诊断规则-查找例如误报,或了解尚未考虑的其他情况(减少误报的数量)。 这些测试还有助于在更新库/工具集的初始阶段跟踪可能的回归。 这次也不例外,因为出现了许多问题。
一个问题是CodeAnalysis库中行为的恶化。 更具体地说,在库代码中的许多项目上,在各种操作(获取语义信息,打开项目等)期间发生了异常。
专注于Visual Studio 2017支持的文章的读者请记住,我们的分发工具包有一个存根-MSBuild.exe文件的大小为0字节。
这次我必须走得更远-现在分发工具包还包含空的编译器存根-csc.exe,vbc.exe,VBCSCompiler.exe。 怎么了 做到这一点的方法是从对测试库中的一个项目进行分析开始,在该项目上出现报告的“差异”-使用新版本的分析仪时,缺少许多警告。
问题原来是条件编译符号-使用新版本的分析仪分析项目时,某些符号提取错误。 为了更好地了解导致此问题的原因,我不得不深入研究Roslyn库。
若要解析条件编译字符,请使用
Microsoft.Build.Tasks.CodeAnalysis库中
Csc类的
GetDefineConstantsSwitch方法。 使用
String.Split方法对许多定界符进行解析:
string[] allIdentifiers = originalDefineConstants.Split(new char[] { ',', ';', ' ' });
这种解析方法可以正常工作,所有必要的条件编译符号都已成功提取。 进一步挖掘。
下一个关键点是对
ToolTask类的
ComputePathToTool方法的调用。 此方法将生成可执行文件(
csc.exe )的路径,并检查其是否存在。 如果文件存在,则返回其路径,否则返回
null 。
来电显示:
.... string pathToTool = ComputePathToTool(); if (pathToTool == null) {
由于没有
csc.exe文件(似乎-我们为什么需要它?),因此
PathToTool在此阶段为
null ,并且当前方法(
ToolTask.Execute )完成执行,结果为
false 。 结果,包括结果条件编译符号在内的任务结果将被忽略。
好吧,让我们看看将
csc.exe文件放在预期位置会发生什么。
在这种情况下,
pathToTool指示现有文件的实际位置,并且继续执行
ToolTask.Execute方法。 下一个关键点是对
ManagedCompiler.ExecuteTool方法的调用。 它开始如下:
protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { if (ProvideCommandLineArgs) { CommandLineArgs = GetArguments(commandLineCommands, responseFileCommands) .Select(arg => new TaskItem(arg)).ToArray(); } if (SkipCompilerExecution) { return 0; } .... }
SkipCompilerExecution属性为
true (从逻辑
上讲 ,我们实际上并未在编译)。 结果,调用方法(已经提到过
ToolTask.Execute )检查
ExecuteTool方法的返回码是否为0,如果是,则以
true值完成其执行。 您在那里拥有
csc.exe的东西-真正的编译器或Leo Tolstoy的“战争与和平”文本格式都没有关系。
结果,主要问题源于以下事实:步骤顺序按以下顺序定义:
并非相反。 编译存根可以成功解决此问题。
好吧,如果未检测到csc.exe文件(并且忽略了任务的结果),成功编译的字符是如何产生的?
对于这种情况,有一种方法
-Microsoft.CodeAnalysis.CSharp库中的
CSharpCommandLineParser.ParseConditionalCompilationSymbols 。
String.Split方法还使用许多定界符来执行解析:
string[] values = value.Split(new char[] { ';', ',' } );
注意
Csc.GetDefineConstantsSwitch方法的定界符集的
区别吗? 在这种情况下,空格不是分隔符。 因此,如果条件编译字符写有空格,则此方法将错误地解析它们。
这种情况出现在有问题的项目上-条件编译字符用空格写在其中,并使用
GetDefineConstantsSwitch成功解析,但不是
ParseConditionalCompilationSymbols解析。
更新库后出现的另一个问题是,在许多情况下,尤其是在未收集的项目上,行为的恶化。 问题出现在Microsoft.CodeAnalysis库中,并以各种异常的形式返回给我们
-ArgumentNullException (某些内部记录器未初始化),
NullReferenceException等。
我想在下面谈论这些问题之一-在我看来,这很有趣。
我们在检查Roslyn项目的最新版本时遇到了这个问题-从其中一个库的代码中引发了
NullReferenceException 。 由于有关问题位置的足够详细的信息,我们很快找到了问题代码,并且出于兴趣的考虑,决定尝试查看在Visual Studio中工作时问题是否再次出现。
很好-可以在Visual Studio中复制它(实验是在Visual Studio 16.0.3上进行的)。 为此,我们需要以下形式的类定义:
class C1<T1, T2> { void foo() { T1 val = default; if (val is null) { } } }
我们还将需要语法可视化程序(.NET编译器平台SDK的一部分)。 有必要从类型
ConstantPatternSyntax (
null )的语法树的节点中请求
TypeSymbol (菜单项“ View TypeSymbol(如果有)”)。 之后,Visual Studio将重新启动,并且在事件查看器中,您可以查看有关该问题的信息,尤其是找到堆栈跟踪:
Application: devenv.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.NullReferenceException at Microsoft.CodeAnalysis.CSharp.ConversionsBase. ClassifyImplicitBuiltInConversionSlow( Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, System.Collections.Generic.HashSet'1 <Microsoft.CodeAnalysis.DiagnosticInfo> ByRef) at Microsoft.CodeAnalysis.CSharp.ConversionsBase.ClassifyBuiltInConversion( Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, Microsoft.CodeAnalysis.CSharp.Symbols.TypeSymbol, System.Collections.Generic.HashSet'1 <Microsoft.CodeAnalysis.DiagnosticInfo> ByRef) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoForNode( Microsoft.CodeAnalysis.CSharp.BoundNode, Microsoft.CodeAnalysis.CSharp.BoundNode, Microsoft.CodeAnalysis.CSharp.BoundNode) at Microsoft.CodeAnalysis.CSharp.MemberSemanticModel.GetTypeInfoWorker( Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.SyntaxTreeSemanticModel.GetTypeInfoWorker( Microsoft.CodeAnalysis.CSharp.CSharpSyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfo( Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoFromNode( Microsoft.CodeAnalysis.SyntaxNode, System.Threading.CancellationToken) at Microsoft.CodeAnalysis.CSharp.CSharpSemanticModel.GetTypeInfoCore( Microsoft.CodeAnalysis.SyntaxNode, System.Threading.CancellationToken) ....
如您所见,问题的原因是空引用的取消引用。
如前所述,我们在分析仪测试期间遇到了相同的问题。 如果使用Microsoft.CodeAnalysis调试库来构建分析器,则可以通过从语法树中的所需节点请求
TypeSymbol进行调试来找到确切的位置。
结果,我们到达了
上面堆栈跟踪中提到的
ClassifyImplicitBuiltInConversionSlow方法:
private Conversion ClassifyImplicitBuiltInConversionSlow( TypeSymbol source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert((object)source != null); Debug.Assert((object)destination != null); if (source.SpecialType == SpecialType.System_Void || destination.SpecialType == SpecialType.System_Void) { return Conversion.NoConversion; } Conversion conversion = ClassifyStandardImplicitConversion(source, destination, ref useSiteDiagnostics); if (conversion.Exists) { return conversion; } return Conversion.NoConversion; }
问题是在这种情况下
目标参数为
null 。 因此,在调用
destination.SpecialType时,将 抛出NullReferenceException 。
是的,Debug.Assert表达式比dereferencing更高,但这还不够,因为实际上它不能防止任何攻击-它仅有助于识别库的调试版本中的问题。还是无济于事。建立C ++专案模型的变更
这里没有什么特别有趣的事情-旧算法不需要任何重大修改,这将是有趣的讨论。也许有两点值得讨论。首先,我们必须修改依赖于ToolsVersion值的算法,以数字格式编写。无需赘述-在某些情况下,您需要比较工具集并选择(例如)最新版本。该版本分别具有较高的数值。进行了计算,与新版本的MSBuild / Visual Studio对应的ToolsVersion等于16.0。不管是什么情况...为了感兴趣,我引用了一个表格,说明在不同版本的Visual Studio中各种属性的值如何变化:当然,这个笑话已经过时了,但是您不禁要记住有关更改Windows和Xbox版本的信息,以了解对于Microsoft而言,预测未来的价值(无论名称和版本如何)是一件摇摇欲坠的事情。 :)
解决方案非常简单-引入了工具集的优先级(分配单独的优先级实体)。第二点是在Visual Studio 2017或相邻环境中工作时的问题(例如,存在VisualStudioVersion环境变量)。事实是,计算构建C ++项目模型所需的参数比构建.NET项目模型复杂得多。对于.NET,我们使用自己的工具集和相应的ToolsVersion值。对于C ++,我们可以在系统中自己和现有的工具集上构建。从Visual Studio 2017中的构建工具开始,工具集已注册在MSBuild.exe.config文件中,不在注册表中。因此,我们无法从常规工具集列表中获取它们(例如,通过Microsoft.Build.Evaluation.ProjectCollection.GlobalProjectCollection.Toolsets),这不同于注册表中记录的那些工具集(对应于<= Visual Studio 2015) 。由于上述原因,使用ToolsVersion 15.0构建项目模型将不起作用,因为系统将看不到必要的工具集。最新工具集- 当前-它是同时提供的,因为这是我们自己的工具集,因此Visual Studio 2019不会出现此类问题。事实证明,该解决方案很简单,无需更改现有的用于构建项目模型的算法即可解决问题-除了Current之外,还可以添加到您自己的工具集列表中(另外一个为15.0)。C#.NET Core项目模型构建中的更改
在此任务的框架内,立即解决了两个问题,因为它们实际上是相关的:- 添加“当前”工具集后,对Visual Studio 2017的.NET Core项目的分析停止了工作;
- 未安装至少一个Visual Studio版本的系统上的.NET Core项目分析无法正常进行。
两种情况下的问题都是相同的-以错误的方式搜索了一些基本的.targets / .props文件。这导致无法使用我们的工具集构建项目模型的事实。在没有Visual Studio中,你可以看到这个错误(以前的版本toolset'a - 15.0): The imported project "C:\Windows\Microsoft.NET\Framework64\ 15.0\Microsoft.Common.props" was not found.
在Visual Studio 2017中构建项目的C#.NET Core模型时,您可能会看到以下问题(使用工具集的当前版本Current): The imported project "C:\Program Files (x86)\Microsoft Visual Studio\ 2017\Community\MSBuild\Current\Microsoft.Common.props" was not found. ....
由于问题相似(但看起来很像),因此您可以尝试用一块石头杀死两只鸟。下面,我描述了如何解决该问题而不涉及技术细节。这些非常详细的信息(关于C#.NET Core项目的构建模型,以及在我们的工具集中更改构建模型)正在我们的未来文章中等待着。顺便说一句,如果您仔细阅读了上面的文字,您可能会注意到这是对以后文章的第二次引用。 :)
那么我们如何解决这个问题呢?解决方案是以.NET Core SDK(Sdk.props,Sdk.targets)中的主要.targets / .props文件为代价来扩展我们自己的工具集。这使我们可以更好地控制这种情况,在管理导入以及总体上构建.NET Core项目模型方面更具灵活性。是的,我们的工具集又增长了一点,我们还必须添加一些逻辑来设置构建.NET Core模型所必需的环境项目,但这似乎是值得的。以前,构建.NET Core项目模型时的工作原理如下:我们只是要求进行此构建,然后一切工作都以MSBuild为代价。现在,当我们将更多控制权掌握在自己手中时,它看起来会有所不同:- 准备构建.NET Core项目模型所需的环境;
- 模型制作:
- 使用我们的工具集中的.targets / .props文件开始构建;
- 使用外部文件继续施工。
通过上述步骤,很明显,设置必要的环境有两个主要目标:- 使用您自己的工具集中的.targets / .props文件启动模型构建;
- 将进一步的操作重定向到外部.targets / .props文件。
为了搜索构建.NET Core项目模型所需的.targets / .props文件,使用了一个特殊的库-Microsoft.DotNet.MSBuildSdkResolver。通过使用此库使用的特殊环境变量解决了使用我们工具集中的文件进行构建的开始-我们建议在哪里(从我们的工具集中)导入必要的文件。由于库是我们分发的一部分,因此不必担心逻辑会突然更改并停止工作。现在,首先从我们的工具集中导入Sdk文件,并且由于我们可以轻松更改它们,因此对构建模型的进一步逻辑的控制权就交到了我们手中。因此,我们可以自己确定需要导入哪些文件以及从何处导入文件。这也适用于上面提到的Microsoft.Common.props。我们从自己的工具集中导入此文件和其他基本文件,并对其可用性和内容充满信心。之后,完成了必要的导入并设置了许多属性后,我们将模型构建的进一步控制转移到实际的.NET Core SDK,其余的必要动作将在该工具上进行。结论
通常,对Visual Studio 2019的支持比对Visual Studio 2017的支持要容易得多,正如我所看到的,这归因于多个因素。首先,Microsoft在Visual Studio 2015和Visual Studio 2017之间的变化不大。是的,我们更改了主要工具集,开始异步定位Visual Studio的插件。第二个-我们已经有了一个带有我们自己的工具集和构建项目模型的解决方案-无需再次发明所有东西,仅是扩展现有解决方案就足够了。由于我们的项目模型构建系统的扩展,对.NET Core项目进行新条件分析(以及在没有安装Visual Studio实例的计算机上进行分析的情况下)的相对简单的支持也使我们希望我们做出了正确的选择。决定控制自己。但是,我还是想重复一下上一篇文章中的想法-有时使用现成的解决方案并不像乍看起来那样简单。
如果您想与讲英语的读者分享这篇文章,请使用以下链接:Sergey Vasiliev。在PVS-Studio中支持Visual Studio 2019