繁体   English   中英

检查Visual Studio项目的一致性

[英]Checking Visual Studio projects for consistency

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

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

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

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

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

另外,您应该检查此帖子以确保始终执行预构建步骤。 本质上,您可以在定制构建步骤中指定空白输出以强制重建。

下表列出了使用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#)

这就是我自己:

一种方法是创建具有错误条件的MSBuild目标:

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

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

我知道的第二种方法是使用诸如VSUnitTest之类的 ,该库提供了一个API,用于可以测试的项目属性。 VSUnitTest当前不是开源的,并且未从NuGet服务中列出。

您可以编写一些代码以文本文件的形式打开解决方案,以标识所有引用的csproj文件,然后依次将每个文件作为xml文件打开,然后编写单元测试以确保项目的特定节点符合您的期望。

这是一种快速而肮脏的解决方案,但适用于CI,并为您提供了忽略不需要的节点的灵活性。 实际上听起来有点有用。 我也有一个我想扫描的35个项目的解决方案。

让我们尝试完全不同的东西:通过从模板生成它们或使用诸如CMake之类的生成工具,可以通过构造确保它们的一致性。 这可能比事实之后尝试使它们保持一致要简单。

在我们的工作中,我们使用一个Powershell脚本来检查项目设置,并在不正确的情况下对其进行修改。 例如,我们以这种方式删除调试配置,禁用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);  
} }`

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

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

  1. 启动Ildasm.exe (IL反汇编程序)。
  2. 加载您要测试的文件。
  3. 如果ILDASM报告该文件不是可移植可执行(PE)文件,则它不是程序集。
    有关更多信息,请参见主题如何:查看装配体内容。

如何以编程方式确定文件是否为程序集

  1. 调用GetAssemblyName方法,传递完整的文件路径和要测试的文件名。
  2. 如果引发BadImageFormatException异常,则该文件不是程序集。

本示例测试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";
}

您可以搜索并用手写C#,脚本,powershell或类似内容替换正则表达式。 但是它存在以下问题:

  • 难以阅读 (三个或三个月以上即可阅读漂亮的正则表达式)
  • 难以增强 (用于新的搜索/替换/检查功能的新正则表达式)
  • 容易破解 (ms生成项目的新发行版/格式或未预测的标记可能不起作用)
  • 难以测试 (您必须检查没有意外的匹配发生)
  • 难以维护 (由于上述原因)

以及以下优点:

  • 不做任何额外的验证 (可能会使它在任何类型的项目(单声道或视觉项目)上均可使用)。
  • 不在乎\\ r :)

最好的办法是使用Microsoft.Build.Evaluation并构建一个执行所有测试/检查/修复等的C#工具。

我已经完成了一个命令行工具 ,该工具使用了源文件列表(供Mono使用)并更新了csproj的源,另一个更新了在控制台上转储csproj内容的源。 这很容易做到,非常简单,也很容易测试。

但是,在通过“非” 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;
    }
}

出于类似的目的,我们使用具有想要在项目之间共享的公共属性的自定义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片段包含到每个项目文件中的唯一缺点是,但是一旦这样做,您将拥有易于管理和更新的模块化构建系统的所有优点。

暂无
暂无

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

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