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

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

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

Ответ 1

Обновлено для .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);
    }
  }
}

Ответ 2

Разумеется, MS предоставляет api для этого без синтаксического разбора xml. Найдите microsoft.build.buildengine

Адаптировано из некоторого кода С# найденного в 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);
    }
  }
}

Ответ 3

Мне понравилась идея использования PowerShell, но чистое XML-решение не работает, поскольку оно выводит цели, определенные в этом файле проекта, а не импорт. Конечно, код С#, о котором все говорят, мертв просто, и с .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 для печати своих целей, а затем мы просто включаем, что ind DefaultDisplayPropertySet.

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

Ответ 4

Я предлагаю вам использовать 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...

Ответ 5

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

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