|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
using Xunit;
using Xunit.Abstractions;
#nullable disable
namespace Microsoft.Build.UnitTests
{
public sealed class MSBuildTask_Tests : IDisposable
{
private readonly ITestOutputHelper _testOutput;
public MSBuildTask_Tests(ITestOutputHelper testOutput)
{
_testOutput = testOutput;
ProjectCollection.GlobalProjectCollection.UnloadAllProjects();
}
public void Dispose()
{
ProjectCollection.GlobalProjectCollection.UnloadAllProjects();
}
/// <summary>
/// If we pass in an item spec that is over the max path but it can be normalized down to something under the max path, we should still work and not
/// throw a path too long exception
/// </summary>
[Fact]
public void ProjectItemSpecTooLong()
{
string currentDirectory = Directory.GetCurrentDirectory();
try
{
Directory.SetCurrentDirectory(Path.GetTempPath());
string tempProject = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetA; TargetB; TargetC` ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`TargetA` Outputs=`a1.dll`/>
<Target Name=`TargetB` Outputs=`b1.dll; b2.dll`/>
<Target Name=`TargetC` Outputs=`@(C_Outputs)`>
<CreateItem Include=`c1.dll` AdditionalMetadata=`MSBuildSourceProjectFile=birch; MSBuildSourceTargetName=oak`>
<Output ItemName=`C_Outputs` TaskParameter=`Include`/>
</CreateItem>
</Target>
</Project>
");
string fileName = Path.GetFileName(tempProject);
string projectFile1 = null;
for (int i = 0; i < 250; i++)
{
projectFile1 += "..\\";
}
int rootLength = Path.GetPathRoot(tempProject).Length;
string tempPathNoRoot = tempProject.Substring(rootLength);
projectFile1 += tempPathNoRoot;
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`Build`>
<MSBuild Projects=`" + projectFile1 + @"`/>
</Target>
</Project>";
try
{
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
bool success = p.Build();
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
}
finally
{
File.Delete(tempProject);
}
}
finally
{
Directory.SetCurrentDirectory(currentDirectory);
}
}
/// <summary>
/// Ensure that the MSBuild task tags any output items with two pieces of metadata -- MSBuildSourceProjectFile and
/// MSBuildSourceTargetName -- that give an indication of where the items came from.
/// </summary>
[Fact]
public void OutputItemsAreTaggedWithProjectFileAndTargetName()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetA; TargetB; TargetC` ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`TargetA` Outputs=`a1.dll`/>
<Target Name=`TargetB` Outputs=`b1.dll; b2.dll`/>
<Target Name=`TargetC` Outputs=`@(C_Outputs)`>
<CreateItem Include=`c1.dll`>
<Output ItemName=`C_Outputs` TaskParameter=`Include`/>
</CreateItem>
</Target>
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetG; TargetH` ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`TargetG` Outputs=`g1.dll; g2.dll`/>
<Target Name=`TargetH` Outputs=`h1.dll`/>
</Project>
");
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + projectFile1 + @"` />
<Projects Include=`" + projectFile2 + @"` />
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
try
{
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
ProjectInstance pi = p.CreateProjectInstance();
IDictionary<string, TargetResult> targetOutputs;
bool success = pi.Build(null, null, null, out targetOutputs);
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
string expectedItemOutputs = string.Format(@"
a1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetA
b1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetB
b2.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetB
c1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetC
g1.dll : MSBuildSourceProjectFile={1} ; MSBuildSourceTargetName=TargetG
g2.dll : MSBuildSourceProjectFile={1} ; MSBuildSourceTargetName=TargetG
h1.dll : MSBuildSourceProjectFile={1} ; MSBuildSourceTargetName=TargetH
", projectFile1, projectFile2);
Assert.True(targetOutputs.ContainsKey("Build"));
Assert.Equal(7, targetOutputs["Build"].Items.Length);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, targetOutputs["Build"].Items, false /* order of items not enforced */);
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
/// <summary>
/// Ensures that it is possible to call the MSBuild task with an empty Projects parameter, and it
/// shouldn't error, and it shouldn't try to build itself.
/// </summary>
[Fact]
public void EmptyProjectsParameterResultsInNoop()
{
string projectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<MSBuild Projects=` @(empty) ` />
</Target>
</Project>
";
MockLogger logger = new MockLogger();
Project project = ObjectModelHelpers.CreateInMemoryProject(projectContents, logger);
bool success = project.Build();
Assert.True(success); // "Build failed. See test output (Attachments in Azure Pipelines) for details"
}
/// <summary>
/// Verifies that nonexistent projects aren't normally skipped
/// </summary>
[Fact]
public void NormallyDoNotSkipNonexistentProjects()
{
ObjectModelHelpers.DeleteTempProjectDirectory();
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"SkipNonexistentProjectsMain.csproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<MSBuild Projects=`this_project_does_not_exist.csproj` />
</Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectFailure(@"SkipNonexistentProjectsMain.csproj", logger);
string error = String.Format(AssemblyResources.GetString("MSBuild.ProjectFileNotFound"), "this_project_does_not_exist.csproj");
Assert.Contains(error, logger.FullLog);
}
/// <summary>
/// Verifies that nonexistent projects aren't normally skipped
/// </summary>
[Fact]
public void NormallyDoNotSkipNonexistentProjectsBuildInParallel()
{
ObjectModelHelpers.DeleteTempProjectDirectory();
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"SkipNonexistentProjectsMain.csproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<MSBuild Projects=`this_project_does_not_exist.csproj` BuildInParallel=`true`/>
</Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectFailure(@"SkipNonexistentProjectsMain.csproj", logger);
string error = String.Format(AssemblyResources.GetString("MSBuild.ProjectFileNotFound"), "this_project_does_not_exist.csproj");
Assert.Equal(0, logger.WarningCount);
Assert.Equal(1, logger.ErrorCount);
Assert.Contains(error, logger.FullLog);
}
/// <summary>
/// Verifies that nonexistent projects are skipped when requested
/// </summary>
[Fact]
public void SkipNonexistentProjects()
{
ObjectModelHelpers.DeleteTempProjectDirectory();
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"SkipNonexistentProjectsMain.csproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<MSBuild Projects=`this_project_does_not_exist.csproj;foo.csproj` SkipNonexistentProjects=`true` />
</Target>
</Project>
");
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"foo.csproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<Message Text=`Hello from foo.csproj`/>
</Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectSuccess(@"SkipNonexistentProjectsMain.csproj", logger);
logger.AssertLogContains("Hello from foo.csproj");
string message = String.Format(AssemblyResources.GetString("MSBuild.ProjectFileNotFoundMessage"), "this_project_does_not_exist.csproj");
string error = String.Format(AssemblyResources.GetString("MSBuild.ProjectFileNotFound"), "this_project_does_not_exist.csproj");
Assert.Equal(0, logger.WarningCount);
Assert.Equal(0, logger.ErrorCount);
Assert.Contains(message, logger.FullLog); // for the missing project
Assert.DoesNotContain(error, logger.FullLog);
}
/// <summary>
/// Verifies that nonexistent projects are skipped when requested when building in parallel.
/// DDB # 125831
/// </summary>
[Fact]
public void SkipNonexistentProjectsBuildingInParallel()
{
ObjectModelHelpers.DeleteTempProjectDirectory();
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"SkipNonexistentProjectsMain.csproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<MSBuild Projects=`this_project_does_not_exist.csproj;foo.csproj` SkipNonexistentProjects=`true` BuildInParallel=`true` />
</Target>
</Project>
");
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"foo.csproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<Message Text=`Hello from foo.csproj`/>
</Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectSuccess(@"SkipNonexistentProjectsMain.csproj", logger);
logger.AssertLogContains("Hello from foo.csproj");
string message = String.Format(AssemblyResources.GetString("MSBuild.ProjectFileNotFoundMessage"), "this_project_does_not_exist.csproj");
string error = String.Format(AssemblyResources.GetString("MSBuild.ProjectFileNotFound"), "this_project_does_not_exist.csproj");
Assert.Equal(0, logger.WarningCount);
Assert.Equal(0, logger.ErrorCount);
Assert.Contains(message, logger.FullLog); // for the missing project
Assert.DoesNotContain(error, logger.FullLog);
}
/// <summary>
/// Verifies that nonexistent projects are skipped when requested when building in parallel.
/// DDB # 125831
/// </summary>
[Fact]
public void SkipNonexistentProjectsAsMetadataBuildingInParallel()
{
ObjectModelHelpers.DeleteTempProjectDirectory();
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"SkipNonexistentProjectsMain.csproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<ItemGroup>
<ProjectReference Include=`this_project_does_not_exist_warn.csproj` >
<SkipNonexistentProjects>true</SkipNonexistentProjects>
</ProjectReference>
<ProjectReference Include=`this_project_does_not_exist_error.csproj` >
</ProjectReference>
<ProjectReference Include=`foo.csproj` >
<SkipNonexistentProjects>false</SkipNonexistentProjects>
</ProjectReference>
</ItemGroup>
<MSBuild Projects=`@(ProjectReference)` BuildInParallel=`true` />
</Target>
</Project>
");
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"foo.csproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<Message Text=`Hello from foo.csproj`/>
</Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectFailure(@"SkipNonexistentProjectsMain.csproj", logger);
logger.AssertLogContains("Hello from foo.csproj");
string message = String.Format(AssemblyResources.GetString("MSBuild.ProjectFileNotFoundMessage"), "this_project_does_not_exist_warn.csproj");
string error = String.Format(AssemblyResources.GetString("MSBuild.ProjectFileNotFound"), "this_project_does_not_exist_warn.csproj");
string error2 = String.Format(AssemblyResources.GetString("MSBuild.ProjectFileNotFound"), "this_project_does_not_exist_error.csproj");
Assert.Equal(0, logger.WarningCount);
Assert.Equal(1, logger.ErrorCount);
Assert.Contains(message, logger.FullLog); // for the missing project
Assert.Contains(error2, logger.FullLog);
Assert.DoesNotContain(error, logger.FullLog);
}
[Fact]
public void LogErrorWhenBuildingVCProj()
{
ObjectModelHelpers.DeleteTempProjectDirectory();
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"BuildingVCProjMain.csproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<MSBuild Projects=`blah.vcproj;foo.csproj` StopOnFirstFailure=`false` />
</Target>
</Project>
");
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"foo.csproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`t` >
<Message Text=`Hello from foo.csproj`/>
</Target>
</Project>
");
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"blah.vcproj",
@"<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<NotWellFormedMSBuildFormatTag />
<Target Name=`t` >
<Message Text=`Hello from blah.vcproj`/>
</Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectFailure(@"BuildingVCProjMain.csproj", logger);
logger.AssertLogContains("Hello from foo.csproj");
string error = String.Format(AssemblyResources.GetString("MSBuild.ProjectUpgradeNeededToVcxProj"), "blah.vcproj");
Assert.Equal(0, logger.WarningCount);
Assert.Equal(1, logger.ErrorCount);
Assert.Contains(error, logger.FullLog);
}
#if FEATURE_COMPILE_IN_TESTS
/// <summary>
/// Regression test for bug 533369. Calling the MSBuild task, passing in a property
/// in the Properties parameter that has a special character in its value, such as semicolon.
/// However, it's a situation where the project author doesn't have control over the
/// property value and so he can't escape it himself.
/// </summary>
[Fact]
public void PropertyOverridesContainSemicolon()
{
ObjectModelHelpers.DeleteTempProjectDirectory();
// -------------------------------------------------------
// ConsoleApplication1.csproj
// -------------------------------------------------------
// Just a normal console application project.
ObjectModelHelpers.CreateFileInTempProjectDirectory(
@"bug'533'369\Sub;Dir\ConsoleApplication1\ConsoleApplication1.csproj", $@"
<Project DefaultTargets=`Build` xmlns=`msbuildnamespace`>
<PropertyGroup>
<Configuration Condition=` '$(Configuration)' == '' `>Debug</Configuration>
<Platform Condition=` '$(Platform)' == '' `>AnyCPU</Platform>
<TargetFrameworkVersion>{MSBuildConstants.StandardTestTargetFrameworkVersion}</TargetFrameworkVersion>
<OutputType>Exe</OutputType>
<AssemblyName>ConsoleApplication1</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=` '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' `>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=` '$(Configuration)|$(Platform)' == 'Release|AnyCPU' `>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Reference Include=`System` />
<Reference Include=`System.Data` />
<Reference Include=`System.Xml` />
</ItemGroup>
<ItemGroup>
<Compile Include=`Program.cs` />
</ItemGroup>
<Import Project=`$(MSBuildBinPath)\Microsoft.CSharp.targets` />
</Project>
");
// -------------------------------------------------------
// Program.cs
// -------------------------------------------------------
// Just a normal console application project.
ObjectModelHelpers.CreateFileInTempProjectDirectory(
@"bug'533'369\Sub;Dir\ConsoleApplication1\Program.cs", @"
using System;
namespace ConsoleApplication32
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(`Hello world`);
}
}
}
");
// -------------------------------------------------------
// TeamBuild.proj
// -------------------------------------------------------
// Attempts to build the above ConsoleApplication1.csproj by calling the MSBuild task,
// and overriding the OutDir property. However, the value being passed into OutDir
// is coming from another property which is produced by CreateProperty and has
// some special characters in it.
ObjectModelHelpers.CreateFileInTempProjectDirectory(
@"bug'533'369\Sub;Dir\TeamBuild.proj", @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`Build`>
<CreateProperty Value=`$(MSBuildProjectDirectory)\binaries\`>
<Output PropertyName=`MasterOutDir` TaskParameter=`Value`/>
</CreateProperty>
<MSBuild Projects=`ConsoleApplication1\ConsoleApplication1.csproj`
Properties=`OutDir=$(MasterOutDir)`
Targets=`Rebuild`/>
</Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectSuccess(@"bug'533'369\Sub;Dir\TeamBuild.proj", logger);
ObjectModelHelpers.AssertFileExistsInTempProjectDirectory(@"bug'533'369\Sub;Dir\binaries\ConsoleApplication1.exe");
}
#endif
/// <summary>
/// Check if passing different global properties via metadata works
/// </summary>
[Fact]
public void DifferentGlobalPropertiesWithDefault()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetA; TargetB` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetA` Outputs=`a1.dll` Condition=`'$(MyProp)'=='0'`/>
<Target Name=`TargetB` Outputs=`b1.dll` Condition=`'$(MyProp)'=='1'`/>
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetG; TargetH` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetG` Outputs=`g1.dll` Condition=`'$(MyProp)'=='0'` />
<Target Name=`TargetH` Outputs=`h1.dll` Condition=`'$(MyProp)'=='1'` />
</Project>
");
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + projectFile1 + @"` />
<Projects Include=`" + projectFile1 + @"`>
<Properties>MyProp=1</Properties>
</Projects>
<Projects Include=`" + projectFile2 + @"` />
<Projects Include=`" + projectFile2 + @"`>
<Properties>MyProp=1</Properties>
</Projects>
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)` Properties=`MyProp=0`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
try
{
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
ProjectInstance pi = p.CreateProjectInstance();
IDictionary<string, TargetResult> targetOutputs;
bool success = pi.Build(null, null, null, out targetOutputs);
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
string expectedItemOutputs = string.Format(@"
a1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetA
b1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetB
g1.dll : MSBuildSourceProjectFile={1} ; MSBuildSourceTargetName=TargetG
h1.dll : MSBuildSourceProjectFile={1} ; MSBuildSourceTargetName=TargetH
", projectFile1, projectFile2);
Assert.True(targetOutputs.ContainsKey("Build"));
Assert.Equal(4, targetOutputs["Build"].Items.Length);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, targetOutputs["Build"].Items, false /* order of items not enforced */);
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
/// <summary>
/// Check if passing different global properties via metadata works
/// </summary>
[Fact]
public void DifferentGlobalPropertiesWithoutDefault()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetA; TargetB` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetA` Outputs=`a1.dll` Condition=`'$(MyProp)'=='0'`/>
<Target Name=`TargetB` Outputs=`b1.dll` Condition=`'$(MyProp)'=='1'`/>
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetG; TargetH` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetG` Outputs=`g1.dll` Condition=`'$(MyProp)'=='0'` />
<Target Name=`TargetH` Outputs=`h1.dll` Condition=`'$(MyProp)'=='1'` />
</Project>
");
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + projectFile1 + @"` />
<Projects Include=`" + projectFile1 + @"`>
<Properties>MyProp=1</Properties>
</Projects>
<Projects Include=`" + projectFile2 + @"` />
<Projects Include=`" + projectFile2 + @"`>
<Properties>MyProp=1</Properties>
</Projects>
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
try
{
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
ProjectInstance pi = p.CreateProjectInstance();
IDictionary<string, TargetResult> targetOutputs;
bool success = pi.Build(null, null, null, out targetOutputs);
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
string expectedItemOutputs = string.Format(@"
b1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetB
h1.dll : MSBuildSourceProjectFile={1} ; MSBuildSourceTargetName=TargetH
", projectFile1, projectFile2);
Assert.True(targetOutputs.ContainsKey("Build"));
Assert.Equal(2, targetOutputs["Build"].Items.Length);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, targetOutputs["Build"].Items, false /* order of items not enforced */);
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
/// <summary>
/// Check trailing semicolons are ignored
/// </summary>
[Fact]
public void VariousPropertiesToMSBuildTask()
{
string projectFile = null;
try
{
projectFile = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<ItemGroup>
<PR Include='$(MSBuildProjectFullPath)'>
<Properties>a=a;b=b;</Properties>
<AdditionalProperties>e=e;g=1;f=f;</AdditionalProperties>
<UndefineProperties>g;h;</UndefineProperties>
</PR>
</ItemGroup>
<Target Name='a'>
<MSBuild Projects='@(PR)' Properties='c=c;d=d;' RemoveProperties='i;c;' Targets='b'/>
</Target>
<Target Name='b'>
<Message Text='a=[$(a)]' Importance='High' />
<Message Text='b=[$(b)]' Importance='High' />
<Message Text='c=[$(c)]' Importance='High' />
<Message Text='d=[$(d)]' Importance='High' />
<Message Text='e=[$(e)]' Importance='High' />
<Message Text='f=[$(f)]' Importance='High' />
<Message Text='g=[$(g)]' Importance='High' />
</Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectSuccess(projectFile, logger);
Console.WriteLine(logger.FullLog);
logger.AssertLogContains("a=[a]");
logger.AssertLogContains("b=[b]");
logger.AssertLogContains("c=[]");
logger.AssertLogContains("d=[]");
logger.AssertLogContains("e=[e]");
logger.AssertLogContains("f=[f]");
logger.AssertLogContains("g=[]");
}
finally
{
File.Delete(projectFile);
}
}
/// <summary>
/// Include and Exclude items outside and inside targets should result in same behavior on
/// platform specific paths.
/// </summary>
[Fact]
public void ItemsIncludeExcludePathsCombinations()
{
string projectFile = null;
try
{
projectFile = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<ItemGroup>
<iout1 Include='a/b.foo' Exclude='a\b.foo' />
<iout2 Include='a\b.foo' Exclude='a/b.foo' />
<iout3 Include='a/b.foo' Exclude='a/b.foo' />
<iout4 Include='a\b.foo' Exclude='a\b.foo' />
<iout5 Include='a/b.foo' Exclude='a\c.foo' />
<iout6 Include='a\b.foo' Exclude='a\c.foo' />
</ItemGroup>
<Target Name='a'>
<ItemGroup>
<iin1 Include='a/b.foo' Exclude='a\b.foo' />
<iin2 Include='a\b.foo' Exclude='a/b.foo' />
<iin3 Include='a/b.foo' Exclude='a/b.foo' />
<iin4 Include='a\b.foo' Exclude='a\b.foo' />
<iin5 Include='a/b.foo' Exclude='a\c.foo' />
<iin6 Include='a\b.foo' Exclude='a\c.foo' />
</ItemGroup>
<Message Text='iout1=[@(iout1)]' Importance='High' />
<Message Text='iout2=[@(iout2)]' Importance='High' />
<Message Text='iout3=[@(iout3)]' Importance='High' />
<Message Text='iout4=[@(iout4)]' Importance='High' />
<Message Text='iout5=[@(iout5)]' Importance='High' />
<Message Text='iout6=[@(iout6)]' Importance='High' />
<Message Text='iin1=[@(iin1)]' Importance='High' />
<Message Text='iin2=[@(iin2)]' Importance='High' />
<Message Text='iin3=[@(iin3)]' Importance='High' />
<Message Text='iin4=[@(iin4)]' Importance='High' />
<Message Text='iin5=[@(iin5)]' Importance='High' />
<Message Text='iin6=[@(iin6)]' Importance='High' />
</Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectSuccess(projectFile, logger);
Console.WriteLine(logger.FullLog);
logger.AssertLogContains("iout1=[]");
logger.AssertLogContains("iout2=[]");
logger.AssertLogContains("iout3=[]");
logger.AssertLogContains("iout4=[]");
logger.AssertLogContains("iout5=[a/b.foo]");
logger.AssertLogContains($"iout6=[a{Path.DirectorySeparatorChar}b.foo]");
logger.AssertLogContains("iin1=[]");
logger.AssertLogContains("iin2=[]");
logger.AssertLogContains("iin3=[]");
logger.AssertLogContains("iin4=[]");
logger.AssertLogContains("iin5=[a/b.foo]");
logger.AssertLogContains($"iin6=[a{Path.DirectorySeparatorChar}b.foo]");
}
finally
{
File.Delete(projectFile);
}
}
/// <summary>
/// Referring to an item outside of target leads to 'naturally expected' reference to the item being processed.
/// No expansion occurs.
/// </summary>
[Fact]
public void ItemsRecursionOutsideTarget()
{
using TestEnvironment env = TestEnvironment.Create();
string projectContent = """
<Project ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<ItemGroup>
<iout1 Include='a/b.foo' TargetPath='%(Filename)%(Extension)' />
<iout1 Include='c/d.foo' TargetPath='%(Filename)%(Extension)' />
<iout1 Include='g/h.foo' TargetPath='%(Filename)%(Extension)' />
</ItemGroup>
<Target Name='a'>
<Message Text="iout1=[@(iout1)]" Importance='High' />
<Message Text="iout1-target-paths=[@(iout1->'%(TargetPath)')]" Importance='High' />
</Target>
</Project>
""";
var projectFile = env.CreateFile("test.proj", ObjectModelHelpers.CleanupFileContents(projectContent));
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectSuccess(projectFile.Path, logger);
_testOutput.WriteLine(logger.FullLog);
logger.AssertLogContains("iout1=[a/b.foo;c/d.foo;g/h.foo]");
logger.AssertLogContains("iout1-target-paths=[b.foo;d.foo;h.foo]");
}
/// <summary>
/// Referring to an item within target leads to item expansion which might be unintended behavior - hence warning.
/// </summary>
[Fact]
public void ItemsRecursionWithinTarget()
{
using TestEnvironment env = TestEnvironment.Create();
string projectContent = """
<Project ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<Target Name='a'>
<ItemGroup>
<iin1 Include='a/b.foo' TargetPath='%(Filename)%(Extension)' />
<iin1 Include='c/d.foo' TargetPath='%(Filename)%(Extension)' />
<iin1 Include='g/h.foo' TargetPath='%(Filename)%(Extension)' />
</ItemGroup>
<Message Text="iin1=[@(iin1)]" Importance='High' />
<Message Text="iin1-target-paths=[@(iin1->'%(TargetPath)')]" Importance='High' />
</Target>
</Project>
""";
string projFileName = "test.proj";
var projectFile = env.CreateFile(projFileName, ObjectModelHelpers.CleanupFileContents(projectContent));
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectSuccess(projectFile.Path, logger);
_testOutput.WriteLine(logger.FullLog);
logger.AssertLogDoesntContain("iin1=[a/b.foo;c/d.foo;g/h.foo]");
logger.AssertLogDoesntContain("iin1-target-paths=[b.foo;d.foo;h.foo]");
logger.AssertLogContains("iin1=[a/b.foo;c/d.foo;g/h.foo;g/h.foo]");
logger.AssertLogContains("iin1-target-paths=[;b.foo;b.foo;d.foo]");
logger.AssertLogContains(string.Format(ResourceUtilities.GetResourceString("ItemReferencingSelfInTarget"), "iin1", "Filename"));
logger.AssertLogContains(string.Format(ResourceUtilities.GetResourceString("ItemReferencingSelfInTarget"), "iin1", "Extension"));
logger.AssertMessageCount("MSB4120", 6);
// The location of the offending attribute (TargetPath) is transferred - for both metadatums (%(Filename) and %(Extension)) on correct locations in xml
logger.AssertMessageCount($"{projFileName}(4,34):", 2, false);
logger.AssertMessageCount($"{projFileName}(5,34):", 2, false);
logger.AssertMessageCount($"{projFileName}(6,34):", 2, false);
Assert.Equal(0, logger.WarningCount);
Assert.Equal(0, logger.ErrorCount);
}
/// <summary>
/// Referring to an unrelated item within target leads to expected expansion.
/// </summary>
[Fact]
public void UnrelatedItemsRecursionWithinTarget()
{
using TestEnvironment env = TestEnvironment.Create();
string projectContent = """
<Project ToolsVersion='msbuilddefaulttoolsversion' xmlns='msbuildnamespace'>
<ItemGroup>
<iout1 Include='a/b.foo'/>
<iout1 Include='c/d.foo'/>
<iout1 Include='g/h.foo'/>
</ItemGroup>
<Target Name='a'>
<ItemGroup>
<iin1 Include='@(iout1)' TargetPath='%(Filename)%(Extension)' />
</ItemGroup>
<Message Text="iin1=[@(iin1)]" Importance='High' />
<Message Text="iin1-target-paths=[@(iin1->'%(TargetPath)')]" Importance='High' />
</Target>
</Project>
""";
var projectFile = env.CreateFile("test.proj", ObjectModelHelpers.CleanupFileContents(projectContent));
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectSuccess(projectFile.Path, logger);
_testOutput.WriteLine(logger.FullLog);
logger.AssertLogContains("iin1=[a/b.foo;c/d.foo;g/h.foo]");
logger.AssertLogContains("iin1-target-paths=[b.foo;d.foo;h.foo]");
logger.AssertLogDoesntContain("MSB4120");
Assert.Equal(0, logger.WarningCount);
Assert.Equal(0, logger.ErrorCount);
}
/// <summary>
/// Check if passing different global properties via metadata works
/// </summary>
[Fact]
public void DifferentGlobalPropertiesWithBlanks()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetA; TargetB` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetA` Outputs=`a1.dll` Condition=`'$(MyProp)'=='0'`/>
<Target Name=`TargetB` Outputs=`b1.dll` Condition=`'$(MyProp)'=='1'`/>
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetG; TargetH` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetG` Outputs=`g1.dll` Condition=`'$(MyProp)'=='0'` />
<Target Name=`TargetH` Outputs=`h1.dll` Condition=`'$(MyProp)'=='1'` />
</Project>
");
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + projectFile1 + @"` />
<Projects Include=`" + projectFile1 + @"`>
<Properties></Properties>
</Projects>
<Projects Include=`" + projectFile2 + @"` />
<Projects Include=`" + projectFile2 + @"`>
<Properties>MyProp=1</Properties>
</Projects>
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
try
{
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
ProjectInstance pi = p.CreateProjectInstance();
IDictionary<string, TargetResult> targetOutputs;
bool success = pi.Build(null, null, null, out targetOutputs);
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
string expectedItemOutputs = string.Format(@"
h1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetH
", projectFile2);
Assert.True(targetOutputs.ContainsKey("Build"));
Assert.Single(targetOutputs["Build"].Items);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, targetOutputs["Build"].Items, false /* order of items not enforced */);
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
/// <summary>
/// Check if passing different global properties via metadata works
/// </summary>
[Fact]
public void DifferentGlobalPropertiesInvalid()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetA; TargetB` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetA` Outputs=`a1.dll` Condition=`'$(MyProp)'=='0'`/>
<Target Name=`TargetB` Outputs=`b1.dll` Condition=`'$(MyProp)'=='1'`/>
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetG; TargetH` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetG` Outputs=`g1.dll` Condition=`'$(MyProp)'=='0'` />
<Target Name=`TargetH` Outputs=`h1.dll` Condition=`'$(MyProp)'=='1'` />
</Project>
");
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + projectFile1 + @"` />
<Projects Include=`" + projectFile1 + @"`>
<Properties>=1</Properties>
</Projects>
<Projects Include=`" + projectFile2 + @"` />
<Projects Include=`" + projectFile2 + @"`>
<Properties>=;1</Properties>
</Projects>
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
try
{
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
bool success = p.Build();
Assert.False(success); // "Build succeeded. See 'Standard Out' tab for details."
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
/// <summary>
/// Check if passing additional global properties via metadata works
/// </summary>
[Fact]
public void DifferentAdditionalPropertiesWithDefault()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetA; TargetB` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetA` Outputs=`a1.dll` Condition=`'$(MyPropG)'=='1'`/>
<Target Name=`TargetB` Outputs=`b1.dll` Condition=`'$(MyPropA)'=='1'`/>
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetG; TargetH` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetG` Outputs=`g1.dll` Condition=`'$(MyPropG)'=='1'` />
<Target Name=`TargetH` Outputs=`h1.dll` Condition=`'$(MyPropA)'=='1'` />
</Project>
");
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + projectFile1 + @"`>
<AdditionalProperties>MyPropA=1</AdditionalProperties>
</Projects>
<Projects Include=`" + projectFile2 + @"`>
<AdditionalProperties>MyPropA=0</AdditionalProperties>
</Projects>
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)` Properties=`MyPropG=1`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
try
{
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
ProjectInstance pi = p.CreateProjectInstance();
IDictionary<string, TargetResult> targetOutputs;
bool success = pi.Build(null, null, null, out targetOutputs);
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
string expectedItemOutputs = string.Format(@"
a1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetA
b1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetB
g1.dll : MSBuildSourceProjectFile={1} ; MSBuildSourceTargetName=TargetG
", projectFile1, projectFile2);
Assert.True(targetOutputs.ContainsKey("Build"));
Assert.Equal(3, targetOutputs["Build"].Items.Length);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, targetOutputs["Build"].Items, false /* order of items not enforced */);
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
/// <summary>
/// Check if passing additional global properties via metadata works
/// </summary>
[Fact]
public void DifferentAdditionalPropertiesWithGlobalProperties()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetA; TargetB` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetA` Outputs=`a1.dll` Condition=`'$(MyPropG)'=='0'`/>
<Target Name=`TargetB` Outputs=`b1.dll` Condition=`'$(MyPropA)'=='1'`/>
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetG; TargetH` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetG` Outputs=`g1.dll` Condition=`'$(MyPropG)'=='0'` />
<Target Name=`TargetH` Outputs=`h1.dll` Condition=`'$(MyPropA)'=='1'` />
</Project>
");
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + projectFile1 + @"`>
<Properties>MyPropG=1</Properties>
<AdditionalProperties>MyPropA=1</AdditionalProperties>
</Projects>
<Projects Include=`" + projectFile2 + @"`>
<Properties>MyPropG=0</Properties>
<AdditionalProperties>MyPropA=1</AdditionalProperties>
</Projects>
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)` Properties=`MyPropG=1`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
try
{
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
ProjectInstance pi = p.CreateProjectInstance();
IDictionary<string, TargetResult> targetOutputs;
bool success = pi.Build(null, null, null, out targetOutputs);
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
string expectedItemOutputs = string.Format(@"
b1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetB
g1.dll : MSBuildSourceProjectFile={1} ; MSBuildSourceTargetName=TargetG
h1.dll : MSBuildSourceProjectFile={1} ; MSBuildSourceTargetName=TargetH
", projectFile1, projectFile2);
Assert.True(targetOutputs.ContainsKey("Build"));
Assert.Equal(3, targetOutputs["Build"].Items.Length);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, targetOutputs["Build"].Items, false /* order of items not enforced */);
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
/// <summary>
/// Check if passing additional global properties via metadata works
/// </summary>
[Fact]
public void DifferentAdditionalPropertiesWithoutDefault()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetA; TargetB` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetA` Outputs=`a1.dll` Condition=`'$(MyPropG)'=='1'`/>
<Target Name=`TargetB` Outputs=`b1.dll` Condition=`'$(MyPropA)'=='1'`/>
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`TargetG; TargetH` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`TargetG` Outputs=`g1.dll` Condition=`'$(MyPropG)'=='1'` />
<Target Name=`TargetH` Outputs=`h1.dll` Condition=`'$(MyPropA)'=='1'` />
</Project>
");
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + projectFile1 + @"`>
<AdditionalProperties>MyPropA=1</AdditionalProperties>
</Projects>
<Projects Include=`" + projectFile2 + @"`>
<AdditionalProperties>MyPropA=1</AdditionalProperties>
</Projects>
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
try
{
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
ProjectInstance pi = p.CreateProjectInstance();
IDictionary<string, TargetResult> targetOutputs;
bool success = pi.Build(null, null, null, out targetOutputs);
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
string expectedItemOutputs = string.Format(@"
b1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetB
h1.dll : MSBuildSourceProjectFile={1} ; MSBuildSourceTargetName=TargetH
", projectFile1, projectFile2);
Assert.True(targetOutputs.ContainsKey("Build"));
Assert.Equal(2, targetOutputs["Build"].Items.Length);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, targetOutputs["Build"].Items, false /* order of items not enforced */);
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
/// <summary>
/// Properties and Targets that use non-standard separation chars
/// </summary>
[Fact]
public void TargetsWithSeparationChars()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`Build` xmlns=`msbuildnamespace` ToolsVersion=`msbuilddefaulttoolsversion`>
<Target Name=`Clean` />
<Target Name=`Build` />
<Target Name=`BuildAgain` />
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`Build` xmlns=`msbuildnamespace` ToolsVersion=`msbuilddefaulttoolsversion`>
<PropertyGroup>
<Targets>Clean%3BBuild%3CBuildAgain</Targets>
</PropertyGroup>
<ItemGroup>
<ProjectFile Include=`" + projectFile1 + @"` />
</ItemGroup>
<Target Name=`Build` Outputs=`$(SomeOutputs)`>
<MSBuild Projects=`@(ProjectFile)` Targets=`$(Targets)` TargetAndPropertyListSeparators=`%3B;%3C` />
</Target>
</Project>
");
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + projectFile2 + @"` />
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
try
{
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
bool success = p.Build();
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
/// <summary>
/// Verify stopOnFirstFailure with BuildInParallel override message are correctly logged
/// Also verify stop on first failure will not build the second project if the first one failed
/// The Aardvark tests which also test StopOnFirstFailure are at:
/// qa\md\wd\DTP\MSBuild\ShippingExtensions\ShippingTasks\MSBuild\_Tst\MSBuild.StopOnFirstFailure
/// </summary>
[Fact]
public void StopOnFirstFailureandBuildInParallelSingleNode()
{
string project1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project xmlns='msbuildnamespace' ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name='msbuild'>
<Error Text='Error'/>
</Target>
</Project>
");
string project2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project xmlns='msbuildnamespace' ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name='msbuild'>
<Message Text='SecondProject'/>
</Target>
</Project>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(project1), new TaskItem(project2)
};
// Test the various combinations of BuildInParallel and StopOnFirstFailure when the msbuild task is told there are not multiple nodes
// running in the system
for (int i = 0; i < 4; i++)
{
bool buildInParallel = false;
bool stopOnFirstFailure = false;
// first set up the project being built.
switch (i)
{
case 0:
buildInParallel = true;
stopOnFirstFailure = true;
break;
case 1:
buildInParallel = true;
stopOnFirstFailure = false;
break;
case 2:
buildInParallel = false;
stopOnFirstFailure = true;
break;
case 3:
buildInParallel = false;
stopOnFirstFailure = false;
break;
}
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + project1 + @"` />
<Projects Include=`" + project2 + @"` />
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)` Targets=`msbuild` BuildInParallel=`" + buildInParallel.ToString() + @"` StopOnFirstFailure=`" + stopOnFirstFailure.ToString() + @"`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
MockLogger logger = new MockLogger();
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents, logger);
bool success = p.Build(logger);
switch (i)
{
case 0:
// Verify setting BuildInParallel and StopOnFirstFailure to
// true will cause the msbuild task to set BuildInParallel to false during the execute
// Verify build did not build second project which has the message SecondProject
logger.AssertLogDoesntContain("SecondProject");
// Verify the correct msbuild task messages are in the log
logger.AssertLogContains(AssemblyResources.GetString("MSBuild.SkippingRemainingProjects"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NoStopOnFirstFailure"));
logger.AssertLogContains(AssemblyResources.GetString("MSBuild.NotBuildingInParallel"));
break;
case 1:
// Verify setting BuildInParallel to true and StopOnFirstFailure to
// false will cause no change in BuildInParallel
// Verify build did build second project which has the message SecondProject
logger.AssertLogContains("SecondProject");
// Verify the correct msbuild task messages are in the log
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingProjects"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NoStopOnFirstFailure"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NotBuildingInParallel"));
break;
case 2:
// Verify build did not build second project which has the message SecondProject
logger.AssertLogDoesntContain("SecondProject");
// Verify the correct msbuild task messages are in the log
logger.AssertLogContains(AssemblyResources.GetString("MSBuild.SkippingRemainingProjects"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NoStopOnFirstFailure"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NotBuildingInParallel"));
break;
case 3:
// Verify setting BuildInParallel to false and StopOnFirstFailure to
// false will cause no change in BuildInParallel
// Verify build did build second project which has the message SecondProject
logger.AssertLogContains("SecondProject");
// Verify the correct msbuild task messages are in the log
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingProjects"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NoStopOnFirstFailure"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NotBuildingInParallel"));
break;
}
// The build should fail as the first project has an error
Assert.False(success, "Iteration of i " + i + " Build Succeeded. See 'Standard Out' tab for details.");
}
}
finally
{
File.Delete(project1);
File.Delete(project2);
}
}
#if FEATURE_APPDOMAIN
/// <summary>
/// Verify stopOnFirstFailure with BuildInParallel override message are correctly logged when there are multiple nodes
/// </summary>
[Fact]
public void StopOnFirstFailureandBuildInParallelMultipleNode()
{
string project1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project xmlns='msbuildnamespace' ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name='msbuild'>
<Error Text='Error'/>
</Target>
</Project>
");
string project2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project xmlns='msbuildnamespace' ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name='msbuild'>
<Message Text='SecondProject'/>
</Target>
</Project>
");
try
{
// Test the various combinations of BuildInParallel and StopOnFirstFailure when the msbuild task is told there are multiple nodes
// running in the system
for (int i = 0; i < 4; i++)
{
bool buildInParallel = false;
bool stopOnFirstFailure = false;
// first set up the project being built.
switch (i)
{
case 0:
buildInParallel = true;
stopOnFirstFailure = true;
break;
case 1:
buildInParallel = true;
stopOnFirstFailure = false;
break;
case 2:
buildInParallel = false;
stopOnFirstFailure = true;
break;
case 3:
buildInParallel = false;
stopOnFirstFailure = false;
break;
}
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + project1 + @"` />
<Projects Include=`" + project2 + @"` />
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)` Targets=`msbuild` BuildInParallel=`" + buildInParallel.ToString() + @"` StopOnFirstFailure=`" + stopOnFirstFailure.ToString() + @"`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
MockLogger logger = new MockLogger();
using ProjectCollection pc = new ProjectCollection(null, new List<ILogger> { logger }, null, ToolsetDefinitionLocations.Default, 2, false);
Project p = ObjectModelHelpers.CreateInMemoryProject(pc, parentProjectContents, logger);
bool success = p.Build();
switch (i)
{
case 0:
// Verify build did build second project which has the message SecondProject
logger.AssertLogContains("SecondProject");
// Verify the correct msbuild task messages are in the log
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingProjects"));
logger.AssertLogContains(AssemblyResources.GetString("MSBuild.NoStopOnFirstFailure"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NotBuildingInParallel"));
break;
case 1:
// Verify setting BuildInParallel to true and StopOnFirstFailure to
// false will cause no change in BuildInParallel
// Verify build did build second project which has the message SecondProject
logger.AssertLogContains("SecondProject");
// Verify the correct msbuild task messages are in the log
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingProjects"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NoStopOnFirstFailure"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NotBuildingInParallel"));
break;
case 2:
// Verify setting BuildInParallel to false and StopOnFirstFailure to
// true will cause no change in BuildInParallel
// Verify build did not build second project which has the message SecondProject
logger.AssertLogDoesntContain("SecondProject");
// Verify the correct msbuild task messages are in the log
logger.AssertLogContains(AssemblyResources.GetString("MSBuild.SkippingRemainingProjects"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NoStopOnFirstFailure"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NotBuildingInParallel"));
break;
case 3:
// Verify setting BuildInParallel to false and StopOnFirstFailure to
// false will cause no change in BuildInParallel
// Verify build did build second project which has the message SecondProject
logger.AssertLogContains("SecondProject");
// Verify the correct msbuild task messages are in the log
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingProjects"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NoStopOnFirstFailure"));
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.NotBuildingInParallel"));
break;
}
// The build should fail as the first project has an error
Assert.False(success, "Iteration of i " + i + " Build Succeeded. See 'Standard Out' tab for details.");
}
}
finally
{
File.Delete(project1);
File.Delete(project2);
}
}
#endif
/// <summary>
/// Test the skipping of the remaining projects. Verify the skip message is only displayed when there are projects to skip.
/// </summary>
[Fact]
public void SkipRemainingProjects()
{
string project1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project xmlns='msbuildnamespace' ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name='msbuild'>
<Error Text='Error'/>
</Target>
</Project>
");
string project2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project xmlns='msbuildnamespace' ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name='msbuild'>
<Message Text='SecondProject'/>
</Target>
</Project>
");
try
{
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + project1 + @"` />
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)` Targets=`msbuild` BuildInParallel=`false` StopOnFirstFailure=`true`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
MockLogger logger = new MockLogger();
using ProjectCollection pc = new ProjectCollection(null, new List<ILogger> { logger }, null, ToolsetDefinitionLocations.Default, 2, false);
Project p = ObjectModelHelpers.CreateInMemoryProject(pc, parentProjectContents, logger);
bool success = p.Build();
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingProjects"));
Assert.False(success); // "Build Succeeded. See 'Standard Out' tab for details."
parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + project2 + @"` />
<Projects Include=`" + project1 + @"` />
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)` Targets=`msbuild` BuildInParallel=`false` StopOnFirstFailure=`true`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
MockLogger logger2 = new MockLogger();
Project p2 = ObjectModelHelpers.CreateInMemoryProject(pc, parentProjectContents, logger2);
bool success2 = p2.Build();
logger2.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingProjects"));
Assert.False(success2); // "Build Succeeded. See 'Standard Out' tab for details."
}
finally
{
File.Delete(project1);
File.Delete(project2);
}
}
/// <summary>
/// Verify the behavior of Target execution with StopOnFirstFailure
/// </summary>
[Fact]
public void TargetStopOnFirstFailureBuildInParallel()
{
string project1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project xmlns='msbuildnamespace' ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name='T1'>
<Message Text='Proj2 T1 message'/>
</Target>
<Target Name='T2'>
<Message Text='Proj2 T2 message'/>
</Target>
<Target Name='T3'>
<Error Text='Error'/>
</Target>
</Project>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(project1)
};
for (int i = 0; i < 6; i++)
{
bool stopOnFirstFailure = false;
bool runEachTargetSeparately = false;
string target1 = String.Empty;
string target2 = String.Empty;
string target3 = String.Empty;
switch (i)
{
case 0:
stopOnFirstFailure = true;
runEachTargetSeparately = true;
target1 = "T1";
target2 = "T2";
target3 = "T3";
break;
case 1:
stopOnFirstFailure = true;
runEachTargetSeparately = true;
target1 = "T1";
target2 = "T3";
target3 = "T2";
break;
case 2:
stopOnFirstFailure = false;
runEachTargetSeparately = true;
target1 = "T1";
target2 = "T3";
target3 = "T2";
break;
case 3:
stopOnFirstFailure = true;
target1 = "T1";
target2 = "T2";
target3 = "T3";
break;
case 4:
stopOnFirstFailure = true;
target1 = "T1";
target2 = "T3";
target3 = "T2";
break;
case 5:
stopOnFirstFailure = false;
target1 = "T1";
target2 = "T3";
target3 = "T2";
break;
}
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + project1 + @"` />
</ItemGroup>
<ItemGroup>
<Targets Include=`" + target1 + @"` />
<Targets Include=`" + target2 + @"` />
<Targets Include=`" + target3 + @"` />
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)` Targets=`@(Targets)` StopOnFirstFailure=`" + stopOnFirstFailure.ToString() + @"` RunEachTargetSeparately=`" + runEachTargetSeparately.ToString() + @"`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
MockLogger logger = new MockLogger();
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents, logger);
bool success = p.Build();
switch (i)
{
case 0:
// Test the case where the error is in the last project and RunEachTargetSeparately = true
logger.AssertLogContains("Proj2 T1 message");
logger.AssertLogContains("Proj2 T2 message");
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingTargets"));
break;
case 1:
// Test the case where the error is in the second target out of 3.
logger.AssertLogContains("Proj2 T1 message");
logger.AssertLogContains(AssemblyResources.GetString("MSBuild.SkippingRemainingTargets"));
logger.AssertLogDoesntContain("Proj2 T2 message");
// The build should fail as the first project has an error
break;
case 2:
// Test case where error is in second last target but stopOnFirstFailure is false
logger.AssertLogContains("Proj2 T1 message");
logger.AssertLogContains("Proj2 T2 message");
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingTargets"));
break;
// Test the cases where RunEachTargetSeparately is false. In these cases all of the targets should be submitted at once
case 3:
// Test the case where the error is in the last project and RunEachTargetSeparately = true
logger.AssertLogContains("Proj2 T1 message");
logger.AssertLogContains("Proj2 T2 message");
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingTargets"));
// The build should fail as the first project has an error
break;
case 4:
// Test the case where the error is in the second target out of 3.
logger.AssertLogContains("Proj2 T1 message");
logger.AssertLogDoesntContain("Proj2 T2 message");
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingTargets"));
// The build should fail as the first project has an error
break;
case 5:
// Test case where error is in second last target but stopOnFirstFailure is false
logger.AssertLogContains("Proj2 T1 message");
logger.AssertLogDoesntContain("Proj2 T2 message");
logger.AssertLogDoesntContain(AssemblyResources.GetString("MSBuild.SkippingRemainingTargets"));
break;
}
// The build should fail as the first project has an error
Assert.False(success, "Iteration of i:" + i + "Build Succeeded. See 'Standard Out' tab for details.");
}
}
finally
{
File.Delete(project1);
}
}
/// <summary>
/// Properties and Targets that use non-standard separation chars
/// </summary>
[Fact]
public void PropertiesWithSeparationChars()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`Build` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`Build` Outputs=`|$(a)|$(b)|$(C)|$(D)|` />
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`Build` xmlns=`msbuildnamespace` ToolsVersion=`msbuilddefaulttoolsversion`>
<PropertyGroup>
<AValues>a%3BA</AValues>
<BValues>b;B</BValues>
<CValues>c;C</CValues>
<DValues>d%3BD</DValues>
</PropertyGroup>
<ItemGroup>
<ProjectFile Include=`" + projectFile1 + @"`>
<AdditionalProperties>C=$(CValues)%3BD=$(DValues)</AdditionalProperties>
</ProjectFile>
</ItemGroup>
<Target Name=`Build` Outputs=`$(SomeOutputs)`>
<MSBuild Projects=`@(ProjectFile)` Targets=`Build` Properties=`a=$(AValues)%3Bb=$(BValues)` TargetAndPropertyListSeparators=`%3B`>
<Output TaskParameter=`TargetOutputs` PropertyName=`SomeOutputs`/>
</MSBuild>
</Target>
</Project>
");
string parentProjectContents = @"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<ItemGroup>
<Projects Include=`" + projectFile2 + @"` />
</ItemGroup>
<Target Name=`Build` Returns=`@(Outputs)`>
<MSBuild Projects=`@(Projects)`>
<Output TaskParameter=`TargetOutputs` ItemName=`Outputs` />
</MSBuild>
</Target>
</Project>";
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(projectFile2)
};
Project p = ObjectModelHelpers.CreateInMemoryProject(parentProjectContents);
ProjectInstance pi = p.CreateProjectInstance();
IDictionary<string, TargetResult> targetOutputs;
bool success = pi.Build(null, null, null, out targetOutputs);
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
Assert.True(targetOutputs.ContainsKey("Build"));
Assert.Equal(5, targetOutputs["Build"].Items.Length);
Assert.Equal("|a", targetOutputs["Build"].Items[0].ItemSpec);
Assert.Equal("A|b", targetOutputs["Build"].Items[1].ItemSpec);
Assert.Equal("B|c", targetOutputs["Build"].Items[2].ItemSpec);
Assert.Equal("C|d", targetOutputs["Build"].Items[3].ItemSpec);
Assert.Equal("D|", targetOutputs["Build"].Items[4].ItemSpec);
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
/// <summary>
/// Orcas had a bug that if the target casing specified was not correct, we would still build it,
/// but not return any target outputs!
/// </summary>
[Fact]
public void TargetNameIsCaseInsensitive()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`Build` xmlns=`msbuildnamespace` ToolsVersion='msbuilddefaulttoolsversion'>
<Target Name=`bUiLd` Outputs=`foo.out` />
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project DefaultTargets=`t` xmlns=`msbuildnamespace` ToolsVersion=`msbuilddefaulttoolsversion`>
<Target Name=`t`>
<MSBuild Projects=`" + projectFile1 + @"` Targets=`BUILD`>
<Output TaskParameter=`TargetOutputs` ItemName=`out`/>
</MSBuild>
<Message Text=`[@(out)]`/>
</Target>
</Project>
");
try
{
Project project = new Project(projectFile2);
MockLogger logger = new MockLogger();
Assert.True(project.Build(logger));
logger.AssertLogContains("[foo.out]");
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
[Fact]
public void ProjectFileWithoutNamespaceBuilds()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project>
<Target Name=`Build` Outputs=`foo.out` />
</Project>
");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project>
<Target Name=`t`>
<MSBuild Projects=`" + projectFile1 + @"` Targets=`Build`>
<Output TaskParameter=`TargetOutputs` ItemName=`out`/>
</MSBuild>
<Message Text=`[@(out)]`/>
</Target>
</Project>
");
try
{
Project project = new Project(projectFile2);
MockLogger logger = new MockLogger();
Assert.True(project.Build(logger));
logger.AssertLogContains("[foo.out]");
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
}
}
|