简体   繁体   English

检查Visual Studio项目的一致性

[英]Checking Visual Studio projects for consistency

You have a large Visual Studio Solution with dozens of project files in it. 您有一个很大的Visual Studio解决方案,其中包含许多项目文件。 How would you verify that all the projects follow certain rules in their property settings, and enforce these rules if a new project is added. 您将如何验证所有项目在其属性设置中均遵循某些规则,并在添加新项目时执行这些规则。 For example check that all projects have: 例如,检查所有项目是否具有:

TargetFrameworkVersion = "v4.5"
Platform = "AnyCPU"
WarningLevel = 4
TreatWarningsAsErrors = true
OutputPath = $(SolutionDir)bin
SignAssembly = true
AssemblyName = $(ProjectFolderName)

I know two methods myself that I will add in an answer below, but I was wondering how people go about doing this type of project test. 我自己知道两种方法,可以在下面的答案中添加,但我想知道人们如何进行这种类型的项目测试。 I'm especially interested to learn about available solutions such as libraries or build tasks for this rather than having to have to invent something new or write it from scratch. 我特别想了解可用的解决方案(例如库)或为此构建任务,而不必非要发明新的东西或从头开始编写它。

*.sln files are plain text and easily parsable, and *.*proj files are xml. * .sln文件是纯文本且易于解析,而*。* proj文件是xml。

You can add a dummy project with a prebuild step that parses the sln to retrieve all of the project files, validate their settings, print a report, and fail the build if necessary. 您可以使用预构建步骤添加一个虚拟项目,该步骤可以解析sln以检索所有项目文件,验证其设置,打印报告并在必要时使构建失败。

Also, you should check this post to ensure the prebuild step is always executed. 另外,您应该检查此帖子以确保始终执行预构建步骤。 Essentially, you specify a blank output in the custom build step to force a rebuild. 本质上,您可以在定制构建步骤中指定空白输出以强制重建。

The following list identifies the key file types that are automatically added to VSS when a solution is added to source control by using the Visual Studio .NET integrated development environment (IDE): 下表列出了使用Visual Studio .NET集成开发环境(IDE)将解决方案添加到源代码管理时将自动添加到VSS的密钥文件类型:

Solution files ( .sln). 解决方案文件( .sln)。 The key items maintained within these files include a list of constituent projects, dependency information, build configuration details, and source control provider details. 这些文件中维护的关键项包括组成项目列表,依赖项信息,构建配置详细信息和源代码控制提供程序详细信息。 Project files ( .csproj or *.vbproj). 项目文件( .csproj或* .vbproj)。 The key items maintained within these files include assembly build settings, referenced assemblies (by name and path), and a file inventory. 这些文件中维护的关键项包括程序集构建设置,引用的程序集(按名称和路径)以及文件清单。 Application configuration files. 应用程序配置文件。 These are configuration files based on Extensible Markup Language (XML) used to control various aspects of your project's run time behavior. 这些是基于可扩展标记语言(XML)的配置文件,用于控制项目运行时行为的各个方面。

Use a Single Solution Model Whenever Possible an 尽可能使用单一解决方案模型

Also see : https://msdn.microsoft.com/en-us/library/ee817677.aspx , https://msdn.microsoft.com/en-us/library/ee817675.aspx 另请参阅: https : //msdn.microsoft.com/en-us/library/ee817677.aspx,https : //msdn.microsoft.com/en-us/library/ee817675.aspx

AND For CONTINUOUS INTEGRATION : there are many tools available like MSBuild, Jenkins, Apache's Continuum, Cruise Control (CC), and Hudson(plugin can be extended to c#) 并且对于连续集成:有许多可用的工具,例如MSBuild,Jenkins,Apache的Continuum,Cruise Control(CC)和Hudson(插件可以扩展到c#)

This is what I have myself: 这就是我自己:

One way to do this is to create an MSBuild target with error conditions: 一种方法是创建具有错误条件的MSBuild目标:

<Error Condition="'$(TreatWarningsAsErrors)'!='true'" Text="Invalid project setting" />

I like this approach because it is integrated with MSBuild and gives you early errors, however, you have to modify every project to import it in them or get all your team members to use a special command prompt with environment variables that will inject custom pre-build steps into your projects during the build, which is a pain. 我喜欢这种方法,因为它与MSBuild集成在一起,并且会给您带来早期错误,但是,您必须修改每个项目以将其导入其中,或者让所有团队成员使用带有特殊变量的命令提示符以及环境变量来注入自定义的在构建过程中将步骤构建到项目中,这很痛苦。

The second approach I know is to use some library like VSUnitTest which provides an API to project properties that you can test against. 我知道的第二种方法是使用诸如VSUnitTest之类的 ,该库提供了一个API,用于可以测试的项目属性。 VSUnitTest is currently not open source and unlisted from the NuGet service. VSUnitTest当前不是开源的,并且未从NuGet服务中列出。

You could write some code to open the the solution as a text file to identify all of the csproj files referenced, in turn opening each of these as xml files, and then writing unit tests to ensure specific nodes of the project match what you expect. 您可以编写一些代码以文本文件的形式打开解决方案,以标识所有引用的csproj文件,然后依次将每个文件作为xml文件打开,然后编写单元测试以确保项目的特定节点符合您的期望。

It's a quick and dirty solution, but works for CI and gives you the flexibility to ignore nodes you don't care about. 这是一种快速而肮脏的解决方案,但适用于CI,并为您提供了忽略不需要的节点的灵活性。 It actually sounds kinda useful. 实际上听起来有点有用。 I have a solution with 35 projects I'd like to scan too. 我也有一个我想扫描的35个项目的解决方案。

Let's try something completely different: you could ensure that they are consistent by construction by generating them from a template or by using a build generation tool such as CMake . 让我们尝试完全不同的东西:通过从模板生成它们或使用诸如CMake之类的生成工具,可以通过构造确保它们的一致性。 This might be simpler than attempting to make them consistent after the fact. 这可能比事实之后尝试使它们保持一致要简单。

In our work we use a powershell script that checks project settings and modified them if they are incorrect. 在我们的工作中,我们使用一个Powershell脚本来检查项目设置,并在不正确的情况下对其进行修改。 For example, we remove Debug configuration this way, disable C++ optimization and SSE2 support. 例如,我们以这种方式删除调试配置,禁用C ++优化和SSE2支持。 We run it manually, but definitely it is possible to run it automatically, eg as pre\\post build step. 我们手动运行它,但是绝对可以自动运行它,例如作为pre \\ post构建步骤。

Below the example: 在示例下面:

`function Prepare-Solution {  
param (  
    [string]$SolutionFolder
)  
$files = gci -Recurse -Path $SolutionFolder -file *.vcxproj | select -    ExpandProperty fullname  
$files | %{  
    $file = $_  
    [xml]$xml = get-content $file  

    #Deleting Debug configurations...
    $xml.Project.ItemGroup.ProjectConfiguration | ?{$_.Configuration -eq "Debug"} | %{$_.ParentNode.RemoveChild($_)} | Out-Null
    $xml.SelectNodes("//*[contains(@Condition,'Debug')]") |%{$_.ParentNode.RemoveChild($_)} | Out-Null

    if($xml.Project.ItemDefinitionGroup.ClCompile) {  
        $xml.Project.ItemDefinitionGroup.ClCompile | %{  
            #Disable SSE2
            if (-not($_.EnableEnhancedInstructionSet)){
                $_.AppendChild($xml.CreateElement("EnableEnhancedInstructionSet", $xml.DocumentElement.NamespaceURI)) | Out-Null  
            }   

            if($_.ParentNode.Condition.Contains("Win32")){  
                $_.EnableEnhancedInstructionSet = "StreamingSIMDExtensions"
            }
            elseif($_.ParentNode.Condition.Contains("x64")) {
                $_.EnableEnhancedInstructionSet = "NotSet"
            } else {
                Write-Host "Neither x86 nor x64 config. Very strange!!"
            }

            #Disable Optimization
            if (-not($_.Optimization)){  
                $_.AppendChild($xml.CreateElement("Optimization", $xml.DocumentElement.NamespaceURI)) | Out-Null  
            }   
            $_.Optimization = "Disabled" 
        } 
    } 
    $xml.Save($file);  
} }`

A file is an assembly if and only if it is managed, and contains an assembly entry in its metadata. 当且仅当文件被管理时,它才是程序集,并且在其元数据中包含程序集条目。 For more information on assemblies and metadata, see the topic Assembly Manifest. 有关程序集和元数据的更多信息,请参见主题程序集清单。

How to manually determine if a file is an assembly 如何手动确定文件是否为程序集

  1. Start the Ildasm.exe (IL Disassembler). 启动Ildasm.exe (IL反汇编程序)。
  2. Load the file you wish to test. 加载您要测试的文件。
  3. If ILDASM reports that the file is not a portable executable (PE) file, then it is not an assembly. 如果ILDASM报告该文件不是可移植可执行(PE)文件,则它不是程序集。
    For more information, see the topic How to: View Assembly Contents. 有关更多信息,请参见主题如何:查看装配体内容。

How to programmatically determine if a file is an assembly 如何以编程方式确定文件是否为程序集

  1. Call the GetAssemblyName method, passing the full file path and name of the file you are testing. 调用GetAssemblyName方法,传递完整的文件路径和要测试的文件名。
  2. If a BadImageFormatException exception is thrown, the file is not an assembly. 如果引发BadImageFormatException异常,则该文件不是程序集。

This example tests a DLL to see if it is an assembly. 本示例测试DLL,以查看其是否为程序集。

class TestAssembly
{
static void Main()
   {

    try
    {
        System.Reflection.AssemblyName testAssembly = System.Reflection.AssemblyName.GetAssemblyName(@"C:\Windows\Microsoft.NET\Framework\v3.5\System.Net.dll");

        System.Console.WriteLine("Yes, the file is an assembly.");
    }

    catch (System.IO.FileNotFoundException)
    {
        System.Console.WriteLine("The file cannot be found.");
    }

    catch (System.BadImageFormatException)
    {
        System.Console.WriteLine("The file is not an assembly.");
    }

    catch (System.IO.FileLoadException)
    {
        System.Console.WriteLine("The assembly has already been loaded.");
    }
   }
}
  // Output (with .NET Framework 3.5 installed):
 // Yes, the file is an assembly.

Framework is the highest installed version, SP is the service pack for that version. Framework是安装的最高版本,SP是该版本的Service Pack。

  RegistryKey installed_versions =   Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP");
  string[] version_names = installed_versions.GetSubKeyNames();
  //version names start with 'v', eg, 'v3.5' which needs to be trimmed off    before conversion
  double Framework = Convert.ToDouble(version_names[version_names.Length - 1].Remove(0, 1), CultureInfo.InvariantCulture);
  int SP =  Convert.ToInt32(installed_versions.OpenSubKey(version_names[version_names.Length     - 1]).GetValue("SP", 0));

 For .Net 4.5


 using System;
 using Microsoft.Win32;


 ...


 private static void Get45or451FromRegistry()
{
using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine,    RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework  Setup\\NDP\\v4\\Full\\")) {
    int releaseKey = Convert.ToInt32(ndpKey.GetValue("Release"));
    if (true) {
        Console.WriteLine("Version: " + CheckFor45DotVersion(releaseKey));
     }
   }
 }


 ...


// Checking the version using >= will enable forward compatibility,  
// however you should always compile your code on newer versions of 
// the framework to ensure your app works the same. 
private static string CheckFor45DotVersion(int releaseKey)
{
if (releaseKey >= 393273) {
   return "4.6 RC or later";
}
if ((releaseKey >= 379893)) {
    return "4.5.2 or later";
}
if ((releaseKey >= 378675)) {
    return "4.5.1 or later";
}
if ((releaseKey >= 378389)) {
    return "4.5 or later";
}
// This line should never execute. A non-null release key should mean 
// that 4.5 or later is installed. 
return "No 4.5 or later version detected";
}

You could go the search & replace Regex way with a handwritten C#, Script, powershell or similar. 您可以搜索并用手写C#,脚本,powershell或类似内容替换正则表达式。 But it has the following problems: 但是它存在以下问题:

  • Difficult to read (Read your pretty regex in three or more months) 难以阅读 (三个或三个月以上即可阅读漂亮的正则表达式)
  • Difficult to enhance (New regex for new search/replace/check feature) 难以增强 (用于新的搜索/替换/检查功能的新正则表达式)
  • Easy to break (a new release/format of ms build project or a not forecast tag may not work) 容易破解 (ms生成项目的新发行版/格式或未预测的标记可能不起作用)
  • Harder to test (you must check that no unintended match occurs) 难以测试 (您必须检查没有意外的匹配发生)
  • Difficult to maintain (because of the above) 难以维护 (由于上述原因)

and the following advantages: 以及以下优点:

  • Not doing any extra validation which (may) let it work on any kind of project (mono or visual). 不做任何额外的验证 (可能会使它在任何类型的项目(单声道或视觉项目)上均可使用)。
  • Doesn't care about \\r :) 不在乎\\ r :)

The best could be to use the Microsoft.Build.Evaluation and build a C# tool which does all your testing/checking/fix and so on. 最好的办法是使用Microsoft.Build.Evaluation并构建一个执行所有测试/检查/修复等的C#工具。

I've done a command line tool that use a sourcefile list (used by Mono) and update sources of csproj and another which dumps on console the csproj content. 我已经完成了一个命令行工具 ,该工具使用了源文件列表(供Mono使用)并更新了csproj的源,另一个更新了在控制台上转储csproj内容的源。 It was easy to do, pretty straightforward and easy to test also. 这很容易做到,非常简单,也很容易测试。

However, it may fail (as I've experienced it) on projects modified by "non" Ms tool (like Mono Studio) or because of missing \\r .... Anyway, you can always handle it with an exception catch and a good message. 但是,在通过“非” MS工具(如Mono Studio)修改的项目上或由于缺少\\ r ...,它可能会失败(以我的经验)。无论如何,您始终可以使用异常捕获和好消息。

Here a sample by using Microsoft.Build.dll (don't use Microsof.Build.Engine as it is obsolete): 这里是使用Microsoft.Build.dll的示例(不要使用Microsof.Build.Engine,因为它已经过时了):

using System;
using Microsoft.Build.Evaluation;

internal class Program
{
    private static void Main(string[] args)
    {
        var project = new Project("PathToYourProject.csproj");
        Console.WriteLine(project.GetProperty("TargetFrameworkVersion", true, string.Empty));
        Console.WriteLine(project.GetProperty("Platform", true, string.Empty));
        Console.WriteLine(project.GetProperty("WarningLevel", true, string.Empty));
        Console.WriteLine(project.GetProperty("TreatWarningsAsErrors", true, "false"));
        Console.WriteLine(project.GetProperty("OutputPath", false, string.Empty));
        Console.WriteLine(project.GetProperty("SignAssembly", true, "false"));
        Console.WriteLine(project.GetProperty("AssemblyName", false, string.Empty));
        Console.ReadLine();
    }
}

public static class ProjectExtensions
{
    public static string GetProperty(this Project project, string propertyName, bool afterEvaluation, string defaultValue)
    {
        var property = project.GetProperty(propertyName);
        if (property != null)
        {
            if (afterEvaluation)
                return property.EvaluatedValue;
            return property.UnevaluatedValue;
        }
        return defaultValue;
    }
}

For similar purposes we use custom MSBuild fragments with common properties that we want to share between the projects, like this ( build.common.props file): 出于类似的目的,我们使用具有想要在项目之间共享的公共属性的自定义MSBuild片段,如下所示( build.common.props文件):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <PropertyGroup>
    <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
    <PlatformToolset>v90</PlatformToolset>
    <OutputPath>$(SolutionDir)..\bin\$(PlatformPath)\$(Configuration)\</OutputPath>

   <!-- whatever you need here -->
  </PropertyGroup>

</Project>

And then we just include this fragment to real VS projects we want to apply these properties to: 然后,我们仅将此片段包含在实际的VS项目中,我们希望将这些属性应用于:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <CommonProps>$(SolutionDir)..\Build\build.common.props</CommonProps>
  </PropertyGroup>

  <Import Project="$(CommonProps)" />

  <!-- the rest of the project -->

</Project>

We handle a lot of things using this approach: 我们使用这种方法处理很多事情:

  • common properties, as you mentioned 您提到的共同属性
  • static analysis (FxCop, StyleCop) 静态分析(FxCop,StyleCop)
  • digital sign of assemblies 组件的数字标牌
  • etc. 等等

The only disadvantage that you need to include these MSBuild fragments into each project file, but once you do that, you have all the benefits of modular build system that is easy to manage and update. 您需要将这些MSBuild片段包含到每个项目文件中的唯一缺点是,但是一旦这样做,您将拥有易于管理和更新的模块化构建系统的所有优点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM