【问题标题】:Checking Visual Studio projects for consistency检查 Visual Studio 项目的一致性
【发布时间】:2015-12-31 22:23:50
【问题描述】:

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

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

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

【问题讨论】:

  • 您是否考虑过在所有项目中包含一个通用文件,以及您提到的设置?这已经减少了任何事情发生的机会。在任何情况下,100% 确定的唯一方法是解析所有项目并检查设置 - 您可以编写 MsBuild 代码来执行此操作(类似于您的答案,但随后会自动运行,因此它会为每个项目自动运行,无需修改项目)或使用Microsoft.Build.Evaluation命名空间中的类来编写一个工具,例如C# 来做到这一点。
  • *.*proj 文件是 XML,您可以编写一个程序来发现任何违反规则的行为,然后采取适当的措施。您也可以将其连接到您选择的 CI 框架中。
  • 您最终找到解决方案了吗?我有一个类似的问题,我想跟踪包中使用的不同版本的 dll。
  • @Sam 不,但我和PSBuild 的作者 Ibrahim Hashimi 有一些 discussion,也许我们可以改进该工具以支持使用 powershell 进行这种验证。

标签: c# powershell msbuild csproj psake


【解决方案1】:

一个文件是一个程序集当且仅当它是被管理的并且在其元数据中包含一个程序集条目。有关程序集和元数据的详细信息,请参阅主题程序集清单。

如何手动判断文件是否为程序集

  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 是该版本的服务包。

  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";
}

【讨论】:

  • 谢谢,但这并不能回答问题。我们不处理任何程序集。项目文件基本上都是xml文件,我们正在寻找除了手动处理之外的解决方案。
  • 也许有一些帮助库。我想这个问题不是我特有的。事实上每一个大型项目都需要有这个测试。如果我必须从头开始创建解决方案,那么我可能会编写一个库供大家使用。
  • @orad 如果你想这样做,那么你必须制作 dll,它可能会在调试时处理 Visual Studio
【解决方案2】:

您可以使用手写的 C#、脚本、powershell 或类似工具来搜索和替换正则表达式。但它存在以下问题:

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

还有以下优点:

  • 不做任何额外的验证,这(可能)让它适用于任何类型的项目(单声道或视觉)。
  • 不在乎\r :)

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

我已经完成a command line tool 使用源文件列表(由 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;
    }
}

【讨论】:

    【解决方案3】:

    出于类似目的,我们使用自定义 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 片段包含到每个项目文件中的唯一缺点,但是一旦您这样做了,您将获得易于管理和更新的模块化构建系统的所有好处。

    【讨论】:

    • 有一种方法可以避免将其添加到每个项目中。将您的自定义 MSBuild 代码放入单独的 .proj 文件,然后使用 CustomBeforeMicrosoftCommonTargets 环境变量指向它。
    • 在我看来,这有点极端。使用环境变量,您可以将单独的 .proj 文件添加到解决方案中的 每个 MSBuild 项目中。这意味着处理各种类型的项目(C#、C++、Sandcastle)和每个项目的不同设置是极其困难的。此外,除非环境变量在用户机器上是全局变量,否则它在 VS 中不起作用。
    【解决方案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);  
    } }`
    

    【讨论】:

      【解决方案5】:

      让我们尝试一些完全不同的方法:您可以通过从模板生成它们或使用诸如CMake 之类的构建生成工具来确保它们是一致的通过构造。这可能比事后试图使它们保持一致更简单。

      【讨论】:

      • 这是一个选项,虽然它有点复杂。
      【解决方案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#)

      【讨论】:

      • 我们使用单一解决方案模型。问题是,当解决方案包含 30 个项目时,如何确保所有项目都具有一致的配置设置,例如针对相同的处理器架构,存储在与 AssemblyName 相同的文件夹名称中,都处理警告作为错误等?
      【解决方案7】:

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

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

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

      【讨论】:

        【解决方案8】:

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

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

        【讨论】:

          【解决方案9】:

          这就是我自己的:

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

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

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

          我知道的第二种方法是使用像 VSUnitTest 这样的库,它提供了一个 API 来投射可以测试的属性。 VSUnitTest 目前不是开源的,并且未从 NuGet 服务中列出。

          【讨论】:

            猜你喜欢
            • 2018-05-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-06-01
            • 1970-01-01
            相关资源
            最近更新 更多