Also, fixes bug #668955.
Add new reserved properties $(MSBuildThisFile*).
Unlike the $(MSBuildProjectFile), the *This* properties are evaluated
in the context where they were used. Eg. if such a property was
referenced in a PropertyGroup, then it would refer to the file
containing the that definition, and *not* the main project file.
It applies to items and tasks/targets also.
* Microsoft.Build.Engine/Microsoft.Build.BuildEngine/BuildChoose.cs:
* Microsoft.Build.Engine/Microsoft.Build.BuildEngine/BuildItemGroup.cs:
* Microsoft.Build.Engine/Microsoft.Build.BuildEngine/BuildPropertyGroup.cs:
Add DefinedInFileName property.
* Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Project.cs:
Add a stack to keep track the "current" file, which is used to set
the $(MSBuildThisFile*) properties. Update the properties on
Push/Pop.
* Microsoft.Build.Engine/Microsoft.Build.BuildEngine/GroupingCollection.cs:
Push/Pop the "current" file, when evaluating a property/item/etc.
* Microsoft.Build.Engine/Microsoft.Build.BuildEngine/Target.cs:
Push/Pop the "current" file, when building. This ensures that the
references in the target definition get evaluated correctly.
* Microsoft.Build.Engine/Test/Microsoft.Build.BuildEngine/ProjectTest.cs:
Add corresponding test.
BuildWhen otherwise;
Project project;
+ ImportedProject importedProject;
XmlElement xmlElement;
List <BuildWhen> whens;
public BuildChoose (XmlElement xmlElement, Project project)
+ : this (xmlElement, project, null)
+ {
+ }
+
+ internal BuildChoose (XmlElement xmlElement, Project project, ImportedProject importedProject)
{
this.xmlElement = xmlElement;
this.project = project;
+ this.importedProject = importedProject;
this.whens = new List <BuildWhen> ();
foreach (XmlNode xn in xmlElement.ChildNodes) {
otherwise = new BuildWhen (xe, project);
}
}
+
+ DefinedInFileName = importedProject != null ? importedProject.FullFileName :
+ project != null ? project.FullFileName : null;
}
public void Evaluate ()
get { return whens; }
set { whens = value; }
}
+
+ internal string DefinedInFileName { get; private set; }
}
}
buildItems.Add (bi);
project.LastItemGroupContaining [bi.Name] = this;
}
+
+ DefinedInFileName = importedProject != null ? importedProject.FullFileName :
+ project != null ? project.FullFileName : null;
}
public BuildItem AddNewItem (string itemName,
}
}
+ internal string DefinedInFileName { get; private set; }
+
internal bool FromXml {
get {
return itemGroupElement != null;
}
} else
this.propertiesByName = new Dictionary <string, BuildProperty> (StringComparer.InvariantCultureIgnoreCase);
+
+ DefinedInFileName = importedProject != null ? importedProject.FullFileName :
+ (project != null ? project.FullFileName : null);
}
public BuildProperty AddNewProperty (string propertyName,
propertiesByName [propertyName] = value;
}
}
-
+
+ internal string DefinedInFileName { get; private set; }
+
internal GroupingCollection GroupingCollection {
get { return parentCollection; }
set { parentCollection = value; }
while (evaluate_iterator != null) {
if (evaluate_iterator.Value is BuildPropertyGroup) {
bpg = (BuildPropertyGroup) evaluate_iterator.Value;
- if (ConditionParser.ParseAndEvaluate (bpg.Condition, project))
- bpg.Evaluate ();
+ project.PushThisFileProperty (bpg.DefinedInFileName);
+ try {
+ if (ConditionParser.ParseAndEvaluate (bpg.Condition, project))
+ bpg.Evaluate ();
+ } finally {
+ project.PopThisFileProperty ();
+ }
}
// if it wasn't moved by adding anything because of evaluating a Import shift it
while (evaluate_iterator != null) {
if (evaluate_iterator.Value is BuildItemGroup) {
big = (BuildItemGroup) evaluate_iterator.Value;
- if (ConditionParser.ParseAndEvaluate (big.Condition, project))
- big.Evaluate ();
+ project.PushThisFileProperty (big.DefinedInFileName);
+ try {
+ if (ConditionParser.ParseAndEvaluate (big.Condition, project))
+ big.Evaluate ();
+ } finally {
+ project.PopThisFileProperty ();
+ }
}
evaluate_iterator = evaluate_iterator.Next;
while (evaluate_iterator != null) {
if (evaluate_iterator.Value is BuildChoose) {
BuildChoose bc = (BuildChoose)evaluate_iterator.Value;
- bool whenUsed = false;
- foreach (BuildWhen bw in bc.Whens) {
- if (ConditionParser.ParseAndEvaluate (bw.Condition, project)) {
- bw.Evaluate ();
- whenUsed = true;
- break;
+ project.PushThisFileProperty (bc.DefinedInFileName);
+ try {
+ bool whenUsed = false;
+ foreach (BuildWhen bw in bc.Whens) {
+ if (ConditionParser.ParseAndEvaluate (bw.Condition, project)) {
+ bw.Evaluate ();
+ whenUsed = true;
+ break;
+ }
}
- }
- if (!whenUsed && bc.Otherwise != null &&
- ConditionParser.ParseAndEvaluate (bc.Otherwise.Condition, project)) {
- bc.Otherwise.Evaluate ();
+ if (!whenUsed && bc.Otherwise != null &&
+ ConditionParser.ParseAndEvaluate (bc.Otherwise.Condition, project)) {
+ bc.Otherwise.Evaluate ();
+ }
+ } finally {
+ project.PopThisFileProperty ();
}
}
bool building;
BuildSettings current_settings;
Stack<Batch> batches;
+
+ // This is used to keep track of "current" file,
+ // which is then used to set the reserved properties
+ // $(MSBuildThisFile*)
+ Stack<string> this_file_property_stack;
ProjectLoadSettings project_load_settings;
initialTargets = new List<string> ();
defaultTargets = new string [0];
batches = new Stack<Batch> ();
+ this_file_property_stack = new Stack<string> ();
globalProperties = new BuildPropertyGroup (null, this, null, false);
foreach (BuildProperty bp in parentEngine.GlobalProperties)
"projectFileName");
this.fullFileName = Utilities.FromMSBuildPath (Path.GetFullPath (projectFileName));
+ PushThisFileProperty (fullFileName);
string filename = fullFileName;
if (String.Compare (Path.GetExtension (fullFileName), ".sln", true) == 0) {
AddPropertyGroup (xe, ip);
break;
case "Choose":
- AddChoose (xe);
+ AddChoose (xe, ip);
break;
default:
throw new InvalidProjectFileException (String.Format ("Invalid element '{0}' in project file.", xe.Name));
projectDir = Path.GetDirectoryName (FullFileName);
evaluatedProperties.AddProperty (new BuildProperty ("MSBuildProjectDirectory", projectDir, PropertyType.Reserved));
+
+ if (this_file_property_stack.Count > 0)
+ // Just re-inited the properties, but according to the stack,
+ // we should have a MSBuild*This* property set
+ SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
}
// precedence:
PropertyGroups.Add (bpg);
}
- void AddChoose (XmlElement xmlElement)
+ void AddChoose (XmlElement xmlElement, ImportedProject importedProject)
{
- BuildChoose bc = new BuildChoose (xmlElement, this);
+ BuildChoose bc = new BuildChoose (xmlElement, this, importedProject);
groupingCollection.Add (bc);
}
return default (T);
}
+ // Used for MSBuild*This* set of properties
+ internal void PushThisFileProperty (string full_filename)
+ {
+ string last_file = this_file_property_stack.Count == 0 ? String.Empty : this_file_property_stack.Peek ();
+ this_file_property_stack.Push (full_filename);
+ if (last_file != full_filename)
+ // first time, or different from previous one
+ SetMSBuildThisFileProperties (full_filename);
+ }
+
+ internal void PopThisFileProperty ()
+ {
+ string last_file = this_file_property_stack.Pop ();
+ if (this_file_property_stack.Count > 0 && last_file != this_file_property_stack.Peek ())
+ SetMSBuildThisFileProperties (this_file_property_stack.Peek ());
+ }
+
+ void SetMSBuildThisFileProperties (string full_filename)
+ {
+ if (String.IsNullOrEmpty (full_filename))
+ return;
+
+ evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFile", Path.GetFileName (full_filename), PropertyType.Reserved));
+ evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileFullPath", full_filename, PropertyType.Reserved));
+ evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileName", Path.GetFileNameWithoutExtension (full_filename), PropertyType.Reserved));
+ evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileExtension", Path.GetExtension (full_filename), PropertyType.Reserved));
+
+ string project_dir = Path.GetDirectoryName (full_filename) + Path.DirectorySeparatorChar;
+ evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectory", project_dir, PropertyType.Reserved));
+ evaluatedProperties.AddProperty (new BuildProperty ("MSBuildThisFileDirectoryNoRoot",
+ project_dir.Substring (Path.GetPathRoot (project_dir).Length),
+ PropertyType.Reserved));
+ }
+
+
internal void LogWarning (string filename, string message, params object[] messageArgs)
{
BuildWarningEventArgs bwea = new BuildWarningEventArgs (
}
bool Build (string built_targets_key, out bool executeOnErrors)
+ {
+ project.PushThisFileProperty (TargetFile);
+ try {
+ return BuildActual (built_targets_key, out executeOnErrors);
+ } finally {
+ project.PopThisFileProperty ();
+ }
+ }
+
+ bool BuildActual (string built_targets_key, out bool executeOnErrors)
{
bool result = false;
executeOnErrors = false;
}
}
+ [Test]
+ public void TestMSBuildThisProperties ()
+ {
+ Engine engine = new Engine (Consts.BinPath);
+ Project project = engine.CreateNewProject ();
+
+ string base_dir = Path.GetFullPath (Path.Combine ("Test", "resources")) + Path.DirectorySeparatorChar;
+ string tmp_dir = Path.GetFullPath (Path.Combine (base_dir, "tmp")) + Path.DirectorySeparatorChar;
+
+ string first_project = Path.Combine (base_dir, "first.proj");
+ string second_project = Path.Combine (tmp_dir, "second.proj");
+ string third_project = Path.Combine (tmp_dir, "third.proj");
+
+ string first = @"<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"" " + Consts.ToolsVersionString + @">
+ <PropertyGroup>
+ <FooInMain>$(MSBuildThisFileDirectory)</FooInMain>
+ </PropertyGroup>
+ <ItemGroup>
+ <ItemInMain Include=""$(MSBuildThisFileFullPath)"" />
+ </ItemGroup>
+ <Import Project=""tmp\second.proj""/>
+ </Project>";
+
+ string second = @"<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"" " + Consts.ToolsVersionString + @">
+ <PropertyGroup>
+ <FooInImport1>$(MSBuildThisFileDirectory)</FooInImport1>
+ </PropertyGroup>
+ <PropertyGroup>
+ <FooInImport2>$(MSBuildThisFileDirectory)</FooInImport2>
+ </PropertyGroup>
+ <ItemGroup>
+ <ItemInImport1 Include=""$(MSBuildThisFileFullPath)"" />
+ </ItemGroup>
+
+ <Import Project=""third.proj""/>
+ </Project>";
+
+ string third = @"<Project xmlns=""http://schemas.microsoft.com/developer/msbuild/2003"" " + Consts.ToolsVersionString + @">
+ <PropertyGroup>
+ <FooInTwo>$(MSBuildThisFileFullPath)</FooInTwo>
+ </PropertyGroup>
+ <ItemGroup>
+ <ItemInTwo Include=""$(MSBuildThisFileFullPath)"" />
+ </ItemGroup>
+
+ <Target Name=""TargetInTwo"">
+ <Message Text=""FooInMain: $(FooInMain)""/>
+ <Message Text=""FooInImport1: $(FooInImport1)""/>
+ <Message Text=""FooInImport2: $(FooInImport2)""/>
+ <Message Text=""FooInTwo: $(FooInTwo)""/>
+
+ <Message Text=""ItemInMain: %(ItemInMain.Identity)""/>
+ <Message Text=""ItemInImport1: %(ItemInImport1.Identity)""/>
+ <Message Text=""ItemInTwo: %(ItemInTwo.Identity)""/>
+ <Message Text=""Full path: $(MSBuildThisFileFullPath)""/>
+ </Target>
+ </Project>";
+
+ File.WriteAllText (first_project, first);
+
+ Directory.CreateDirectory (Path.Combine (base_dir, "tmp"));
+ File.WriteAllText (second_project, second);
+ File.WriteAllText (third_project, third);
+
+ MonoTests.Microsoft.Build.Tasks.TestMessageLogger logger =
+ new MonoTests.Microsoft.Build.Tasks.TestMessageLogger ();
+ engine.RegisterLogger (logger);
+
+ project.Load (first_project);
+ try {
+ Assert.IsTrue (project.Build (), "Build failed");
+
+ logger.CheckLoggedMessageHead ("FooInMain: " + base_dir, "A1");
+ logger.CheckLoggedMessageHead ("FooInImport1: " + tmp_dir, "A2");
+ logger.CheckLoggedMessageHead ("FooInImport2: " + tmp_dir, "A3");
+ logger.CheckLoggedMessageHead ("FooInTwo: " + third_project, "A4");
+ logger.CheckLoggedMessageHead ("ItemInMain: " + first_project, "A5");
+ logger.CheckLoggedMessageHead ("ItemInImport1: " + second_project, "A6");
+ logger.CheckLoggedMessageHead ("ItemInTwo: " + third_project, "A7");
+ logger.CheckLoggedMessageHead ("Full path: " + third_project, "A8");
+
+ Assert.AreEqual (0, logger.NormalMessageCount, "Unexpected extra messages found");
+ } catch {
+ logger.DumpMessages ();
+ throw;
+ } finally {
+ File.Delete (first_project);
+ File.Delete (second_project);
+ File.Delete (third_project);
+ }
+ }
[Test]
public void TestRequiredTask_String1 ()