Как запросить файл MSBUILD для получения списка поддерживаемых целей?

Есть ли способ спросить msbuild, какие цели сборки обеспечивали поддержку файла msbuild? Если нет способа сделать это в командной строке? Может быть, это можно сделать программно?

Нет ли способа сделать это, кроме синтаксического анализа msbuild XML?

5 ответов

Решение

Обновлен для.NET Framework 4, поскольку вышесказанное устарело. Импортируйте microsoft.build.dll и код таков:

using System;
using Microsoft.Build.Evaluation;
class MyTargets
{
  static void Main(string[] args)
  {
    Project project = new Project(args[0]);
    foreach (string target in project.Targets.Keys)
    {
      Console.WriteLine("{0}", target);
    }
  }
}

Конечно, MS предоставляет API для этого, не разбирая xml самостоятельно. Посмотрите на microsoft.build.buildengine

Адаптировано из некоторого кода C#, найденного в msdn... обычно его стоит изучить. Для компиляции необходимо обратиться к dll microsoft.build.engine. Замените версию фреймворка и путь ниже вашими значениями. Это сработало на примере файла проекта, хотя список может быть длиннее, чем вы ожидаете.

using System;
using Microsoft.Build.BuildEngine;
class MyTargets
{        
  static void Main(string[] args)
  {
    Engine.GlobalEngine.BinPath = @"C:\Windows\Microsoft.NET\Framework\v2.0.NNNNN";
    Project project = new Project();
    project.Load(@"c:\path\to\my\project.proj");
    foreach (Target target in project.Targets)
    {
      Console.WriteLine("{0}", target.Name);
    }
  }
}

Мне понравилась идея использования PowerShell, но чистое решение XML не работает, потому что оно выводит только цели, определенные в этом файле проекта, а не импортирует. Конечно, код C#, на который все продолжают ссылаться, очень прост, а в.Net 4.5 это две строки (первую из которых вы должны рассмотреть просто добавление в свой профиль):

Add-Type -As Microsoft.Build
New-Object Microsoft.Build.Evaluation.Project $Project | Select -Expand Targets 

Да уж. В самом деле. Вот и все.

Поскольку вывод очень подробный, вы можете захотеть ограничить то, на что вы смотрите:

New-Object Microsoft.Build.Evaluation.Project $Project | 
    Select -Expand Targets |
    Format-Table Name, DependsOnTargets -Wrap

Однако есть одна загвоздка.

Когда вы загружаете такие сборки, они втыкаются в GlobalProjectCollection до тех пор, пока вы оставляете это окно PowerShell открытым, и вы не можете открыть его снова, пока не выгрузите их. Чтобы разгрузить их:

[Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.UnloadAllProjects()

Учитывая это, возможно, стоит включить это в функцию, которая может принимать частичные и относительные пути или даже переданные по конвейеру файлы проекта в качестве входных данных:

Add-Type -As Microsoft.Build
Update-TypeData -DefaultDisplayPropertySet Name, DependsOnTargets -TypeName Microsoft.Build.Execution.ProjectTargetInstance

function Get-Target {
    param(
        # Path to project file (supports pipeline input and wildcards)
        [Parameter(ValueFromPipelineByPropertyName=$true, ValueFromPipeline=$true, Position=1)]
        [Alias("PSPath")]
        [String]$Project,

        # Filter targets by name. Supports wildcards
        [Parameter(Position=2)]
        [String]$Name = "*"

    )
    begin {
        # People do funny things with parameters
        # Lets make sure they didn't pass a Project file as the name ;)
        if(-not $Project -and $Name -ne "*") {
            $Project = Resolve-Path $Name
            if($Project) { $Name = "*" }
        }
        if(-not $Project) {
            $Project = Get-Item *.*proj
        }
    }
    process {
        Write-Host "Project: $_ Target: $Name"
        Resolve-Path $Project | % {
            # Unroll the ReadOnlyDictionary to get the values so we can filter ...
            (New-Object Microsoft.Build.Evaluation.Project "$_").Targets.Values.GetEnumerator()
        } | Where { $_.Name -like $Name }
    }
    end {
        [microsoft.build.evaluation.projectcollection]::globalprojectcollection.UnloadAllProjects()
    }
}

И теперь вам даже не нужно вручную форматировать таблицу...

Приложение:

Очевидно, что вы можете добавить все, что вы хотите, к выходу с этим Update-TypeData, например, если вы хотите увидеть условия, или, возможно, BeforeTargets или AfterTargets...

Вы даже можете получить вложенную информацию. Например, вы можете заменить Update-TypeData позвоните выше с этими двумя:

Update-TypeData -MemberName CallTargets -MemberType ScriptProperty -Value {
    $this.Children | ? Name -eq "CallTarget" | %{ $_.Parameters["Targets"] } 
} -TypeName Microsoft.Build.Execution.ProjectTargetInstance

Update-TypeData -DefaultDisplayPropertySet Name, DependsOnTargets, CallTargets -TypeName Microsoft.Build.Execution.ProjectTargetInstance

Вы видите, что первый добавляет вычисленное свойство CallTargets, которое перечисляет прямые дочерние элементы и ищет задачи CallTarget, чтобы напечатать их цели, а затем мы просто включаем это DefaultDisplayPropertySet,

ПРИМЕЧАНИЕ. Вдобавок к этому потребовалось бы много логики, чтобы увидеть каждую цель, которая будет выполняться при построении какой-либо конкретной цели (для этого нам придется рекурсивно обрабатывать DependsOnTargets, и нам также нужно искать любые цели, которые имеют эту цель в своих объектах BeforeTargets или AfterTargets (также рекурсивно), и это прежде, чем мы перейдем к задачам, которые могут фактически просто вызывать цели, такие как CallTargets и MSBuild... и все эти вещи могут зависеть от условий, настолько сложных, что это невозможно сказать, что произойдет, не выполнив это на самом деле;)

Я предлагаю вам использовать PowerShell:

Select-Xml `
    -XPath //b:Target `
    -Path path-to-build-file `
    -Namespace @{ b = 'http://schemas.microsoft.com/developer/msbuild/2003' } |
    Select-Object -ExpandProperty Node |
    Format-Table -Property Name, DependsOnTargets -AutoSize

Запрос XPath найдет все Target элементы и отображают имя цели и зависимости в табличном формате. Вот пример, который выбирает 10 первых целей из Microsoft.Web.Publishing.targets:

PS C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v11.0\Web> Select-Xml `
    -XPath //b:Target `
    -Path Microsoft.Web.Publishing.targets `
    -Namespace @{ b = 'http://schemas.microsoft.com/developer/msbuild/2003' } |
    Select-Object -ExpandProperty Node |
    Sort-Object -Property Name |
    Select-Object -First 10 |
    Format-Table -Property Name, DependsOnTargets -AutoSize

Name                                    DependsOnTargets                       
----                                    ----------------                       
_CheckPublishToolsUpToDate                                                     
_CheckRemoteFx45                                                               
_CleanWPPIfNeedTo                                                              
_DetectDbDacFxProvider                                                         
_WPPCopyWebApplication                  $(_WPPCopyWebApplicationDependsOn)     
AddContentPathToSourceManifest          $(AddContentPathToSourceManifestDepe...
AddDeclareParametersItems               $(AddDeclareParametersItemsDependsOn)  
AddDeclareParametersItemsForContentPath $(AddDeclareParametersItemsForConten...
AddDeclareParametersItemsForIis6        $(AddDeclareParametersItemsForIis6De...
AddDeclareParametersItemsForIis7        $(AddDeclareParametersItemsForIis7De...

Вот фрагмент кода для получения всех целей в порядке их выполнения.

    static void Main(string[] args)
    {
        Project project = new Project(@"build.core.xml");
        var orderedTargets = GetAllTargetsInOrderOfExecution(project.Targets, project.Targets["FinalTargetInTheDependencyChain"]).ToList();
        File.WriteAllText(@"orderedTargets.txt", orderedTargets.Select(x => x.Name).Aggregate((a, b) => a + "\r\n" + b));
    }

    /// <summary>
    /// Gets all targets in the order of their execution by traversing through the dependent targets recursively
    /// </summary>
    /// <param name="allTargetsInfo"></param>
    /// <param name="target"></param>
    /// <returns></returns>
    public static List<ProjectTargetInstance> GetAllTargetsInOrderOfExecution(IDictionary<string, ProjectTargetInstance> allTargetsInfo, ProjectTargetInstance target)
    {
        var orderedTargets = new List<ProjectTargetInstance>();

        var dependentTargets =
            target
            .DependsOnTargets
            .Split(';')
            .Where(allTargetsInfo.ContainsKey)
            .Select(x => allTargetsInfo[x])
            .ToList();

        foreach (var dependentTarget in dependentTargets)
        {
            orderedTargets = orderedTargets.Union(GetAllTargetsInOrderOfExecution(allTargetsInfo, dependentTarget)).ToList();
        }

        orderedTargets.Add(target);

        return orderedTargets;
    }

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

Диагностический файл не очень удобен для чтения, но содержит всю необходимую информацию в формате XML.

Например, из CMD и типичной установки VS2017; замените строку 1 любым используемым вами build-tool-loader

call "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
set MSBuildEmitSolution=1
call MSBuild SolutionFile.sln /t:rebuild   

В качестве реального примера я создал файл SLN, начинающийся с проекта C#, который называется mixed_projтак что у меня есть mixed_proj.sln, mixed_proj.csprojЗатем два проекта C++, которые я добавил, ConsoleApplication1 а также DLL1,

Я установил порядок зависимости сборки где ConsoleApplication1 строит только после DLL1

Вот командная строка для его сборки:

call "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat"
set MSBuildEmitSolution=1
call MSBuild miced_proj.sln /t:rebuild 

Вот сгенерированное содержимое файла метапроекта (слишком большой для вставки)

Другие вопросы по тегам