rneatherway
rneatherway

Reputation: 553

Run MSBuild Task programatically and access the output

I'm trying to run an MSBuild task, in this case ResolveAssemblyReferences, and get access to the outputs of the task such as ResolvedFiles. A short F# script that loads the project (a default F# project created with VS2013) and runs the task is below. With the log verbosity set to Diagnostic, I can see that the task runs successfully, and resolves all the assemblies correctly.

#r @"C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Build.dll"
#r @"C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Build.Framework.dll"

open System
open Microsoft.Build

let p = new Evaluation.Project("d:/dev/fsharptest/Test/Test.fsproj")

let log = Logging.ConsoleLogger()
log.Verbosity <- Framework.LoggerVerbosity.Diagnostic

p.Build([|"ResolveProjectReferences";"ResolveAssemblyReferences"|],
        Seq.singleton (log :> Framework.ILogger) )

for i in p.AllEvaluatedProperties do
  printfn "%s: %s" i.Name i.EvaluatedValue

However, neither the evaluated properties nor the items contain any of the outputs of ResolveAssemblyReferences, which is what I am after. The file Microsoft.Common.CurrentVersion.targets has as one output of ResolveAssemblyReferences <Output TaskParameter="ResolvedFiles" ItemName="ReferencePath"/>, but I cannot access this value.

How should I go about getting hold of it?

Upvotes: 1

Views: 1193

Answers (2)

rneatherway
rneatherway

Reputation: 553

It turns out that Evaluation.Project and Execution.ProjectInstance are rather different. I tried to use the former, but the latter is closest to the obsolete BuildEngine.Project class I was previously using. The following code snippet returns the fully resolved references for a given project file:

#r @"C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Build.dll"
#r @"C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Build.Framework.dll"

open System
open Microsoft.Build

let p = new Execution.ProjectInstance(@"d:\dev\fsharptest\Test\Test.fsproj")

p.Build([|"ResolveReferences"|], new Collections.Generic.HashSet<_>())

for i in p.GetItems("ReferencePath") do
  printfn "%s" i.EvaluatedInclude

It is in fact possible to get an arbitrary output for the target in question in this manner.

Upvotes: 3

JDługosz
JDługosz

Reputation: 5642

I think the problem is that some arbitrary Output parameter of the Task you end up running is not what the MSBuild task itself returns. It gathers up the "target Returns" of the Tasks you specify directly.

However, I don't know exactly how your syntax works here: you are giving Task names rather than Targets?

But based on what I've read (entry 37 in Kretzler’s book) you could have a Target defined to run the desired Task and hook up the task output to the target’s Return attribute. Then the MSBuild task, told to run that target, will pass through the Return attribute as its own.

I think that would be something like:

<Target Name="ResolveAssemblyReferences" ⋯
   Returns="@(ReferencePath)" >

So if the Task you are calling from within that Target is populating the Item Array named ReferencePath as its output parameter, then you publish that same item array as the Target's return value.

If you don't use Returns anywhere in the build script, then the Outputs are automatically taken as the returns.

If you can't edit ResolveAssemblyReferences, then I said you can make a new Target which depends on it. Since ReferencePath is global after the task completes, the new target will still see them and can return it.

If all else fails, have your build script write the item list to a file, which you can then load from some other program without concern over what MSBuild is returning.

Upvotes: 1

Related Questions