How to query MSBUILD file for list of supported targets?

后端 未结 6 444
遥遥无期
遥遥无期 2020-12-14 01:43

Is there any way to ask msbuild what build targets provided msbuild file support? If there is no way to do it in command prompt? May be it could be done programmatically?

6条回答
  •  慢半拍i
    慢半拍i (楼主)
    2020-12-14 02:17

    I loved the idea of using PowerShell, but the pure XML solution doesn't work because it only outputs targets defined in that project file, and not imports. Of course, the C# code everyone keeps referring to is dead simple, and with .Net 4.5 it's two lines (the first of which you should consider just adding to your profile):

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

    Yeah. Really. That's the whole thing.

    Since the output is very verbose, you might want to restrict what you're looking at:

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

    There is a catch, however.

    When you load builds like that, they stick in the GlobalProjectCollection as long as you leave that PowerShell window open and you can't re-open them until you unload them. To unload them:

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

    Considering that, it might be worth wrapping that in a function that can accept partial and relative paths or even piped project files as input:

    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()
        }
    }
    

    And now you don't even need to manually Format-Table...

    Addendum:

    Obviously you can add anything you want to the output with that Update-TypeData, for instance, if you wanted to see Conditions, or maybe BeforeTargets or AfterTargets...

    You could even pull nested information. For instance, you could replace the Update-TypeData call above with these two:

    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
    

    You see the first one adds a calculated CallTargets property that enumerates the direct children and looks for CallTarget tasks to print their Targets, and then we just include that ind the DefaultDisplayPropertySet.

    NOTE: It would take a lot of logic on top of this to see every target that's going to be executed when you build any particular target (for that, we'd have to recursively process the DependsOnTargets and we'd also need to look for any targets that have this target in their BeforeTargets or AfterTargets (also recursively), and that's before we get to tasks that can actually just call targets, like CallTargets and MSBuild ... and all of these things can depend on conditions so complex that it's impossible to tell what's going to happen without actually executing it ;)

提交回复
热议问题