Reputation: 34840
When implementing a microsoft.build.utilities.task how to i get access to the various environmental variables of the build?
For example "TargetPath"
I know i can pass it in as part of the task XML
<MyTask TargetPath="$(TargetPath)" />
But i don't want to force the consumer of the task to have to do that if I can access the variable in code.
http://msdn.microsoft.com/en-us/library/microsoft.build.utilities.task.aspx
Upvotes: 3
Views: 1466
Reputation: 171
Building upon Simon's answer, which
_
I ended up with the following. Note, the ProjectInstance
type is considered 'implementation specific' and so you have to add a PackageReference
to Microsoft.Build
to access it.
public static class BuildEngineExtensions
{
private const BindingFlags BindingFlags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public;
public static ProjectInstance GetProjectInstance(this IBuildEngine buildEngine)
{
var buildEngineType = buildEngine.GetType();
var targetBuilderCallbackField = buildEngineType.GetField("_targetBuilderCallback", BindingFlags);
if (targetBuilderCallbackField is null)
throw new InvalidOperationException($"Could not extract _targetBuilderCallback from {buildEngineType.FullName}");
var targetBuilderCallback = targetBuilderCallbackField.GetValue(buildEngine);
var targetCallbackType = targetBuilderCallback!.GetType();
var projectInstanceField = targetCallbackType.GetField("_projectInstance", BindingFlags);
if (projectInstanceField is null)
throw new InvalidOperationException($"Could not extract _projectInstance from {targetCallbackType.FullName}");
if (projectInstanceField.GetValue(targetBuilderCallback) is not ProjectInstance project)
throw new InvalidOperationException();
return project;
}
}
I found that ProjectInstance
contains public definitions for GetProperty
and GetItems
so I didn't bother using reflection to access those.
For convenience, and to mitigate the overhead of getting the ProjectInstance
by reflection, I defined a TaskBase
class:
public abstract class TaskBase : Microsoft.Build.Utilities.Task
{
private ProjectInstance? _project;
protected ProjectInstance Project => _project ??= BuildEngine.GetProjectInstance();
protected string ProjectName => Project.GetPropertyValue("ProjectName");
}
Which I found worked perfectly!
public class MyTask : TaskBase
{
public void Execute()
{
Log.LogMessage(MessageImportance.High, $"This task was called by {ProjectName}!");
return true;
}
}
Upvotes: 1
Reputation: 34840
I worked out how to do this
public static class BuildEngineExtensions
{
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public;
public static IEnumerable GetEnvironmentVariable(this IBuildEngine buildEngine, string key,bool throwIfNotFound)
{
var projectInstance = GetProjectInstance(buildEngine);
var items = projectInstance.Items
.Where(x => string.Equals(x.ItemType, key, StringComparison.InvariantCultureIgnoreCase)).ToList();
if (items.Count > 0)
{
return items.Select(x => x.EvaluatedInclude);
}
var properties = projectInstance.Properties
.Where(x => string.Equals(x.Name, key, StringComparison.InvariantCultureIgnoreCase)).ToList();
if (properties.Count > 0)
{
return properties.Select(x => x.EvaluatedValue);
}
if (throwIfNotFound)
{
throw new Exception(string.Format("Could not extract from '{0}' environmental variables.", key));
}
return Enumerable.Empty();
}
static ProjectInstance GetProjectInstance(IBuildEngine buildEngine)
{
var buildEngineType = buildEngine.GetType();
var targetBuilderCallbackField = buildEngineType.GetField("targetBuilderCallback", bindingFlags);
if (targetBuilderCallbackField == null)
{
throw new Exception("Could not extract targetBuilderCallback from " + buildEngineType.FullName);
}
var targetBuilderCallback = targetBuilderCallbackField.GetValue(buildEngine);
var targetCallbackType = targetBuilderCallback.GetType();
var projectInstanceField = targetCallbackType.GetField("projectInstance", bindingFlags);
if (projectInstanceField == null)
{
throw new Exception("Could not extract projectInstance from " + targetCallbackType.FullName);
}
return (ProjectInstance)projectInstanceField.GetValue(targetBuilderCallback);
}
}
And it can be used like this
string targetPath = buildEngine.GetEnvironmentVariable("TargetPath", true).First();
string intermediateAssembly = buildEngine.GetEnvironmentVariable("IntermediateAssembly", true).First();
IEnumerable<string> referencePaths = buildEngine.GetEnvironmentVariable("ReferencePath", true);
Yes it is ugly and black magic but it works.
Upvotes: 7
Reputation: 49970
You can not do this easily and you shouldn't do it. A task shouldn't know its context of execution, and should work with its input parameters.
If you really want to do it, you would need to reparse the project file with something like that.
public override bool Execute()
{
string projectFile = BuildEngine.ProjectFileOfTaskNode;
Engine buildEngine = new Engine(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory());
Project project = new Project(buildEngine);
project.Load(projectFile);
foreach(var o in project.EvaluatedProperties)
{
// Use properties
}
// Do what you want
return true;
}
Upvotes: 1