Проверка проектов Visual Studio на согласованность

У вас есть большое решение Visual Studio с десятками файлов проекта. Как вы можете проверить, что все проекты соответствуют определенным правилам в настройках своих свойств, и обеспечить соблюдение этих правил при добавлении нового проекта. Например, убедитесь, что во всех проектах есть:

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

Я сам знаю два метода, которые я добавлю в ответ ниже, но мне было интересно, как люди проводят этот тип тестирования проекта. Мне особенно интересно узнать о доступных решениях, таких как библиотеки или задачи сборки для этого, вместо того, чтобы придумывать что-то новое или писать с нуля.


person orad    schedule 03.10.2015    source источник
comment
Рассматривали ли вы включение одного общего файла во все проекты с указанными вами настройками? Это уже уменьшило бы вероятность того, что что-то не так. В любом случае единственный способ быть уверенным на 100% - это проанализировать все проекты и проверить настройки - вы можете написать для этого код MsBuild (что-то вроде вашего ответа, но затем автоматически, чтобы он запускался автоматически для каждого проекта и без необходимости изменять проекты) или используйте классы в пространстве имен Microsoft.Build.Evaluation, чтобы написать инструмент, например C # для этого.   -  person stijn    schedule 10.10.2015
comment
Файлы *. * Proj представляют собой XML, вы можете написать программу, которая обнаружит любые нарушения ваших правил, а затем предпримет соответствующие действия. Вы также можете подключить это к выбранной вами структуре CI.   -  person Chris O    schedule 12.10.2015
comment
Вы нашли решение для этого? У меня аналогичная проблема, когда я хочу отслеживать разные версии dll, используемые в пакетах.   -  person Sam    schedule 30.11.2015
comment
@Sam нет, но у меня было несколько обсуждения с Ибрагимом Хашими, автором PSBuild, возможно, мы могли бы улучшить этот инструмент для поддержки такого рода проверки с помощью PowerShell.   -  person orad    schedule 01.12.2015


Ответы (9)


Файлы * .sln представляют собой простой текст и легко поддаются синтаксическому анализу, а файлы *. * proj - это xml.

Вы можете добавить фиктивный проект с этапом предварительной сборки, который анализирует sln для получения всех файлов проекта, проверки их настроек, печати отчета и, если необходимо, сбоя сборки.

Кроме того, вы должны проверить этот пост, чтобы этап предварительной сборки всегда выполнялся. По сути, вы указываете пустой вывод на настраиваемом шаге сборки, чтобы принудительно выполнить перестройку.

person kidmosey    schedule 14.10.2015

В следующем списке указаны типы файлов ключей, которые автоматически добавляются в VSS, когда решение добавляется в систему управления версиями с помощью интегрированной среды разработки (IDE) Visual Studio .NET:

Файлы решения (.sln). Ключевые элементы, поддерживаемые в этих файлах, включают список составляющих проектов, информацию о зависимостях, сведения о конфигурации сборки и сведения о поставщике системы управления версиями. Файлы проекта ( .csproj или * .vbproj). Ключевые элементы, поддерживаемые в этих файлах, включают параметры сборки сборки, сборки, на которые имеются ссылки (по имени и пути), и инвентаризацию файлов. Файлы конфигурации приложения. Это файлы конфигурации на основе Extensible Markup Language (XML), используемые для управления различными аспектами поведения вашего проекта во время выполнения.

По возможности используйте модель единого решения

См. Также: https://msdn.microsoft.com/en-us/library/ee817677.aspx, https://msdn.microsoft.com/en-us/library/ee817675.aspx

И для НЕПРЕРЫВНОЙ ИНТЕГРАЦИИ: доступно множество инструментов, таких как MSBuild, Jenkins, Apache's Continuum, Cruise Control (CC) и Hudson (плагин можно расширить до C #)

person Ryu    schedule 15.10.2015
comment
Мы используем модель единого решения. Вопрос в том, когда решение состоит из 30 проектов, как убедиться, что все они имеют согласованные параметры конфигурации, такие как ориентация на одну и ту же архитектуру процессора, хранятся в той же папке, что и AssemblyName, все обрабатывают предупреждения как ошибки и тд? - person orad; 15.10.2015

Вот что у меня есть:

Один из способов сделать это - создать цель MSBuild с условиями ошибки:

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

Мне нравится этот подход, потому что он интегрирован с MSBuild и дает вам ранние ошибки, однако вам нужно изменить каждый проект, чтобы импортировать его в них, или заставить всех членов вашей команды использовать специальную командную строку с переменными среды, которые будут вводить пользовательские предварительные настройки. встраивайте шаги в свои проекты во время сборки, что является болью.

Второй известный мне подход - использовать некоторую библиотеку, например VSUnitTest, которая предоставляет API для проектирования свойств, с которыми вы можете тестировать. VSUnitTest в настоящее время не является открытым исходным кодом и не включен в список службы NuGet.

person orad    schedule 03.10.2015

Вы можете написать код, чтобы открыть решение в виде текстового файла, чтобы идентифицировать все упомянутые файлы csproj, в свою очередь открывая каждый из них как файлы xml, а затем записывая модульные тесты, чтобы убедиться, что конкретные узлы проекта соответствуют тому, что вы ожидаете.

Это быстрое и грязное решение, но оно работает для CI и дает вам гибкость, позволяющую игнорировать узлы, которые вам не нужны. На самом деле это звучит довольно полезно. У меня есть решение с 35 проектами, которые я тоже хотел бы просканировать.

person Sam    schedule 12.10.2015

Давайте попробуем что-нибудь совершенно другое: вы можете убедиться, что они согласованы по конструкции, сгенерировав их из шаблона или используя инструмент создания сборки, такой как CMake. Это может быть проще, чем пытаться сделать их последовательными постфактум.

person Richard Cook    schedule 16.10.2015
comment
Это вариант, хотя он немного сложнее. - person orad; 16.10.2015

В своей работе мы используем сценарий PowerShell, который проверяет настройки проекта и изменяет их, если они неверны. Например, таким образом мы удаляем конфигурацию Debug, отключаем оптимизацию C ++ и поддержку SSE2. Мы запускаем его вручную, но, безусловно, можно запустить его автоматически, например как этап до \ после сборки.

Ниже пример:

`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);  
} }`
person olegk    schedule 16.10.2015

Файл является сборкой тогда и только тогда, когда он управляется и содержит запись сборки в своих метаданных. Дополнительные сведения о сборках и метаданных см. В разделе «Манифест сборки».

Как вручную определить, является ли файл сборкой

  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";
}
person Manraj    schedule 13.10.2015
comment
Спасибо, но это не ответ на вопрос. Никакими сборками мы не занимаемся. Файлы проекта в основном представляют собой файлы xml, и мы ищем другое решение, кроме обработки их вручную. - person orad; 13.10.2015
comment
Может быть, с какой-нибудь вспомогательной библиотекой. Полагаю, эта проблема не для меня. Фактически, каждый крупный проект должен пройти этот тест. Если мне нужно создать решение с нуля, я, вероятно, напишу для него библиотеку, чтобы все могли ее использовать. - person orad; 15.10.2015
comment
@orad, если вы хотите это сделать, вам нужно создать dll, который может обрабатывать Visual Studio при отладке - person Manraj; 17.10.2015

Для аналогичных целей мы используем настраиваемые фрагменты 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 в каждый файл проекта, но как только вы это сделаете, вы получите все преимущества модульной системы сборки, которой легко управлять и обновлять.

person Dmitry Kolomiets    schedule 17.10.2015
comment
Есть способ избежать добавления этого в каждый проект. Поместите свой собственный код MSBuild в отдельный файл .proj, затем используйте переменную среды CustomBeforeMicrosoftCommonTargets, чтобы указать на него. - person orad; 19.10.2015
comment
На мой взгляд, это немного экстремально. С помощью переменной среды вы добавляете отдельный файл .proj в каждый проект MSBuild в своем решении. Это означает, что чрезвычайно сложно обрабатывать различные типы проектов (C #, C ++, Sandcastle) и различные настройки для каждого проекта. Кроме того, он не работает изнутри VS, если переменная среды не является глобальной на компьютере пользователя. - person Dmitry Kolomiets; 29.10.2016

Вы можете выполнить поиск и заменить Regex на рукописный C #, Script, 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;
    }
}
person Fab    schedule 17.10.2015