检查Visual Studio项目的一致性

时间:2015-10-03 23:52:40

标签: c# powershell msbuild csproj psake

您有一个包含大量项目文件的大型Visual Studio解决方案。如何验证所有项目是否遵循其属性设置中的某些规则,并在添加新项目时强制执行这些规则。例如,检查所有项目是否具有:

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

我自己知道两种方法,我将在下面的答案中添加,但我想知道人们如何进行这种类型的项目测试。我特别感兴趣的是要了解可用的解决方案,例如库或为此构建任务,而不必从头开发新内容或从头开始编写。

9 个答案:

答案 0 :(得分:9)

*。sln文件是纯文本,易于解析,*。* proj文件是xml。

您可以使用prebuild步骤添加虚拟项目,该步骤解析sln以检索所有项目文件,验证其设置,打印报告以及在必要时使构建失败。

此外,您应该检查this post以确保始终执行预建步骤。实质上,您在自定义生成步骤中指定一个空白输出以强制重建。

答案 1 :(得分:6)

以下列表标识了使用Visual Studio .NET集成开发环境(IDE)将解决方案添加到源代码管理时自动添加到VSS的密钥文件类型:

解决方案文件( .sln)。这些文件中维护的关键项包括组成项目列表,依赖关系信息,构建配置详细信息和源控制提供程序详细信息。 项目文件( .csproj或* .vbproj)。这些文件中维护的关键项包括程序集构建设置,引用的程序集(按名称和路径)和文件清单。 应用配置文件。这些是基于可扩展标记语言(XML)的配置文件,用于控制项目运行时行为的各个方面。

尽可能使用单一解决方案模型

另见:https://msdn.microsoft.com/en-us/library/ee817677.aspx,            https://msdn.microsoft.com/en-us/library/ee817675.aspx

和连续整合:  有许多工具,如MSBuild,Jenkins,Apache的Continuum,Cruise Control(CC)和Hudson(插件可以扩展到c#)

答案 2 :(得分:4)

这就是我自己:

执行此操作的一种方法是创建具有错误条件的MSBuild目标:

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

我喜欢这种方法,因为它与MSBuild集成并为您提供早期错误,但是,您必须修改每个项目以将其导入其中,或者让您的所有团队成员使用特殊的命令提示符以及将注入的环境变量在构建过程中自定义预构建步骤到您的项目中,这很痛苦。

我知道的第二种方法是使用像VSUnitTest这样的库,它为项目属性提供API,您可以对其进行测试。 VSUnitTest目前不是NuGet服务的开源和未列出的。

答案 3 :(得分:4)

您可以编写一些代码来打开解决方案作为文本文件来识别引用的所有csproj文件,然后将每个文件作为xml文件打开,然后编写单元测试以确保项目的特定节点匹配什么你期待。

这是一个快速而肮脏的解决方案,但适用于CI,可让您灵活地忽略您不关心的节点。它实际上听起来很有用。我有一个我想要扫描的35个项目的解决方案。

答案 4 :(得分:4)

让我们尝试一些完全不同的东西:您可以通过从模板生成它们或使用构建生成工具(例如CMake)来确保它们与构造一致。这可能比在事后确保它们保持一致更简单。

答案 5 :(得分:4)

在我们的工作中,我们使用powershell脚本来检查项目设置,如果它们不正确则修改它们。例如,我们以这种方式删除Debug配置,禁用C ++优化和SSE2支持。我们手动运行它,但绝对可以自动运行它,例如作为pre \ post构建步骤。

示例下方:

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

答案 6 :(得分:3)

当且仅当文件被管理时,文件才是程序集,并且在其元数据中包含程序集条目。有关程序集和元数据的更多信息,请参阅Assembly Manifest主题。

如何手动确定文件是否为程序集

  1. 启动Ildasm.exe(IL反汇编程序)。
  2. 加载您要测试的文件。
  3. 如果ILDASM报告该文件不是可移植可执行(PE)文件,则它不是程序集。
    有关详细信息,请参阅如何:查看装配内容。
  4. 如何以编程方式确定文件是否为程序集

    1. 调用GetAssemblyName方法,传递您正在测试的文件的完整文件路径和名称。
    2. 如果抛出BadImageFormatException异常,则该文件不是程序集。
    3. 此示例测试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是安装的最高版本,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";
      }
      

答案 7 :(得分:1)

你可以去搜索&amp;用手写的C#,Script,powershell或类似方法替换Regex方式。但它有以下问题:

  • 难以阅读(在三个月或更长时间内阅读漂亮的正则表达式)
  • 难以提升(新搜索/替换/检查功能的新正则表达式)
  • 易于破解(ms版本项目的新版本/格式或非预测代码可能无效)
  • 难以测试(您必须检查是否发生无意外匹配)
  • 难以维护(由于上述原因)

以及以下优点:

  • 不进行任何额外验证,它可以让它适用于任何类型的项目(单声道或视觉效果)。
  • 不关心\ r :)

最好的方法是使用Microsoft.Build.Evaluation 并构建一个C#工具,用于完成所有测试/检查/修复等工作。

我已完成a command line tool使用源文件列表(由Mono使用)并更新csproj的源代码和另一个在控制台上转储csproj内容的源代码。它很容易做到,非常简单,也很容易测试。

然而,对于由&#34; non&#34;修改的项目,它可能会失败(因为我已经经历过)。 Ms工具(如Mono Studio)或因缺少 \ r .... 无论如何,你总是可以通过异常捕获和好消息来处理它。

这里有一个使用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;
    }
}

答案 8 :(得分:1)

出于类似目的,我们使用具有我们想要在项目之间共享的公共属性的自定义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>

然后我们将这个片段包含在我们想要将这些属性应用到的真实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>

我们使用这种方法处理很多事情:

  • 常见属性,正如您所提到的
  • 静态分析(FxCop,StyleCop)
  • 集会的数字标志

您需要将这些MSBuild片段包含到每个项目文件中的唯一缺点,但是一旦您这样做,您就可以获得易于管理和更新的模块化构建系统的所有好处。