|
// 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.IO;
using System.Linq;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;
using Shouldly;
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;
try
{
MSBuild msbuildTask = new MSBuild
{
BuildEngine = new MockEngine(_testOutput),
Projects = new ITaskItem[] { new TaskItem(projectFile1) }
};
msbuildTask.Execute().ShouldBeTrue("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>
");
try
{
MSBuild msbuildTask = new MSBuild();
msbuildTask.BuildEngine = new MockEngine();
msbuildTask.Projects = new ITaskItem[] { new TaskItem(projectFile1), new TaskItem(projectFile2) };
bool success = msbuildTask.Execute();
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);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, msbuildTask.TargetOutputs, 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 = ObjectModelHelpers.CleanupFileContents(@"
<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);
Assert.Contains("MSB3202", logger.FullLog); // project file not found
}
/// <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);
Assert.Equal(0, logger.WarningCount);
Assert.Equal(1, logger.ErrorCount);
Assert.Contains("MSB3202", logger.FullLog); // project file not found
}
/// <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");
Assert.Equal(0, logger.WarningCount);
Assert.Equal(0, logger.ErrorCount);
Assert.Contains("this_project_does_not_exist.csproj", logger.FullLog); // for the missing project
Assert.DoesNotContain("MSB3202", logger.FullLog); // project file not found error
}
/// <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");
Assert.Equal(0, logger.WarningCount);
Assert.Equal(0, logger.ErrorCount);
Assert.Contains("this_project_does_not_exist.csproj", logger.FullLog); // for the missing project
Assert.DoesNotContain("MSB3202", logger.FullLog); // project file not found error
}
/// <summary>
/// </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");
Assert.Equal(0, logger.WarningCount);
Assert.Equal(1, logger.ErrorCount);
Assert.Contains("MSB3204", logger.FullLog); // upgrade to vcxproj needed
}
/// <summary>
/// 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>
#if RUNTIME_TYPE_NETCORE
[Fact(Skip = "https://github.com/dotnet/msbuild/issues/259")]
#else
[Fact]
#endif
public void PropertyOverridesContainSemicolon()
{
ObjectModelHelpers.DeleteTempProjectDirectory();
// -------------------------------------------------------
// ConsoleApplication1.csproj
// -------------------------------------------------------
// Just a normal console application project.
ObjectModelHelpers.CreateFileInTempProjectDirectory(
Path.Combine("bug'533'369", "Sub;Dir", "ConsoleApplication1", "ConsoleApplication1.csproj"), $@"
<Project DefaultTargets=`Build` xmlns=`msbuildnamespace`>
<Import Project=`$(MSBuildBinPath)\Microsoft.Common.props` />
<PropertyGroup>
<Configuration Condition=` '$(Configuration)' == '' `>Debug</Configuration>
<TargetFrameworkVersion>{MSBuildConstants.StandardTestTargetFrameworkVersion}</TargetFrameworkVersion>
<Platform Condition=` '$(Platform)' == '' `>AnyCPU</Platform>
<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(
Path.Combine("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(
Path.Combine("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(Path.Combine("bug'533'369", "Sub;Dir", "TeamBuild.proj"), logger);
ObjectModelHelpers.AssertFileExistsInTempProjectDirectory(Path.Combine("bug'533'369", "Sub;Dir", "binaries", "ConsoleApplication1.exe"));
}
/// <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>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(projectFile1),
new TaskItem(projectFile1),
new TaskItem(projectFile2),
new TaskItem(projectFile2)
};
projects[1].SetMetadata("Properties", "MyProp=1");
projects[3].SetMetadata("Properties", "MyProp=1");
MSBuild msbuildTask = new MSBuild();
msbuildTask.BuildEngine = new MockEngine();
msbuildTask.Projects = projects;
msbuildTask.Properties = new string[] { "MyProp=0" };
bool success = msbuildTask.Execute();
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);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, msbuildTask.TargetOutputs, 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>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(projectFile1),
new TaskItem(projectFile1),
new TaskItem(projectFile2),
new TaskItem(projectFile2)
};
projects[1].SetMetadata("Properties", "MyProp=1");
projects[3].SetMetadata("Properties", "MyProp=1");
MSBuild msbuildTask = new MSBuild();
msbuildTask.BuildEngine = new MockEngine();
msbuildTask.Projects = projects;
bool success = msbuildTask.Execute();
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);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, msbuildTask.TargetOutputs, 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 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>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(projectFile1),
new TaskItem(projectFile1),
new TaskItem(projectFile2),
new TaskItem(projectFile2)
};
projects[1].SetMetadata("Properties", "");
projects[3].SetMetadata("Properties", "MyProp=1");
MSBuild msbuildTask = new MSBuild();
msbuildTask.BuildEngine = new MockEngine();
msbuildTask.Projects = projects;
bool success = msbuildTask.Execute();
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
string expectedItemOutputs = string.Format(@"
h1.dll : MSBuildSourceProjectFile={0} ; MSBuildSourceTargetName=TargetH
", projectFile2);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, msbuildTask.TargetOutputs, 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>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(projectFile1),
new TaskItem(projectFile1),
new TaskItem(projectFile2),
new TaskItem(projectFile2)
};
projects[1].SetMetadata("Properties", "=1");
projects[3].SetMetadata("Properties", "=;1");
MSBuild msbuildTask = new MSBuild();
msbuildTask.BuildEngine = new MockEngine();
msbuildTask.Projects = projects;
bool success = msbuildTask.Execute();
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>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(projectFile1),
new TaskItem(projectFile2)
};
projects[0].SetMetadata("AdditionalProperties", "MyPropA=1");
projects[1].SetMetadata("AdditionalProperties", "MyPropA=0");
MSBuild msbuildTask = new MSBuild();
msbuildTask.BuildEngine = new MockEngine();
msbuildTask.Properties = new string[] { "MyPropG=1" };
msbuildTask.Projects = projects;
bool success = msbuildTask.Execute();
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);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, msbuildTask.TargetOutputs, 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>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(projectFile1),
new TaskItem(projectFile2)
};
projects[0].SetMetadata("AdditionalProperties", "MyPropA=1");
projects[1].SetMetadata("AdditionalProperties", "MyPropA=1");
projects[0].SetMetadata("Properties", "MyPropG=1");
projects[1].SetMetadata("Properties", "MyPropG=0");
MSBuild msbuildTask = new MSBuild();
msbuildTask.BuildEngine = new MockEngine();
msbuildTask.Projects = projects;
bool success = msbuildTask.Execute();
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);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, msbuildTask.TargetOutputs, 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>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(projectFile1),
new TaskItem(projectFile2)
};
projects[0].SetMetadata("AdditionalProperties", "MyPropA=1");
projects[1].SetMetadata("AdditionalProperties", "MyPropA=1");
MSBuild msbuildTask = new MSBuild();
msbuildTask.BuildEngine = new MockEngine();
msbuildTask.Projects = projects;
bool success = msbuildTask.Execute();
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);
ObjectModelHelpers.AssertItemsMatch(expectedItemOutputs, msbuildTask.TargetOutputs, 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>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(projectFile2)
};
MSBuild msbuildTask = new MSBuild();
msbuildTask.BuildEngine = new MockEngine();
msbuildTask.Projects = projects;
bool success = msbuildTask.Execute();
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++)
{
MSBuild msbuildTask = new MSBuild();
// By default IsMultipleNodesIs false
MockEngine mockEngine = new MockEngine();
mockEngine.IsRunningMultipleNodes = false;
msbuildTask.BuildEngine = mockEngine;
msbuildTask.Projects = projects;
msbuildTask.Targets = new string[] { "msbuild" };
// Make success true as the expected result is false
bool success = true;
switch (i)
{
case 0:
// Verify setting BuildInParallel and StopOnFirstFailure to
// true will cause the msbuild task to set BuildInParallel to false during the execute
msbuildTask.BuildInParallel = true;
msbuildTask.StopOnFirstFailure = true;
success = msbuildTask.Execute();
// Verify build did not build second project which has the message SecondProject
mockEngine.AssertLogDoesntContain("SecondProject");
// Verify the correct msbuild task messages are in the log
Assert.False(msbuildTask.BuildInParallel); // "Iteration of 0 Expected BuildInParallel to be false"
break;
case 1:
// Verify setting BuildInParallel to true and StopOnFirstFailure to
// false will cause no change in BuildInParallel
msbuildTask.BuildInParallel = true;
msbuildTask.StopOnFirstFailure = false;
success = msbuildTask.Execute();
// Verify build did build second project which has the message SecondProject
mockEngine.AssertLogContains("SecondProject");
// Verify the correct msbuild task messages are in the log
Assert.True(msbuildTask.BuildInParallel); // "Iteration of 1 Expected BuildInParallel to be true"
break;
case 2:
// Verify setting BuildInParallel to false and StopOnFirstFailure to
// true will cause no change in BuildInParallel
msbuildTask.BuildInParallel = false;
msbuildTask.StopOnFirstFailure = true;
success = msbuildTask.Execute();
// Verify build did not build second project which has the message SecondProject
mockEngine.AssertLogDoesntContain("SecondProject");
// Verify the correct msbuild task messages are in the log
Assert.False(msbuildTask.BuildInParallel); // "Iteration of 2 Expected BuildInParallel to be false"
break;
case 3:
// Verify setting BuildInParallel to false and StopOnFirstFailure to
// false will cause no change in BuildInParallel
msbuildTask.BuildInParallel = false;
msbuildTask.StopOnFirstFailure = false;
success = msbuildTask.Execute();
// Verify build did build second project which has the message SecondProject
mockEngine.AssertLogContains("SecondProject");
// Verify the correct msbuild task messages are in the log
Assert.False(msbuildTask.BuildInParallel); // "Iteration of 3 Expected BuildInParallel to be false"
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
{
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 multiple nodes
// running in the system
for (int i = 0; i < 4; i++)
{
MSBuild msbuildTask = new MSBuild();
MockEngine mockEngine = new MockEngine();
mockEngine.IsRunningMultipleNodes = true;
msbuildTask.BuildEngine = mockEngine;
msbuildTask.Projects = projects;
msbuildTask.Targets = new string[] { "msbuild" };
// Make success true as the expected result is false
bool success = true;
switch (i)
{
case 0:
// Verify setting BuildInParallel and StopOnFirstFailure to
// true will not cause the msbuild task to set BuildInParallel to false during the execute
msbuildTask.BuildInParallel = true;
msbuildTask.StopOnFirstFailure = true;
success = msbuildTask.Execute();
// Verify build did build second project which has the message SecondProject
mockEngine.AssertLogContains("SecondProject");
// Verify the correct msbuild task messages are in the log
Assert.True(msbuildTask.BuildInParallel); // "Iteration of 0 Expected BuildInParallel to be true"
break;
case 1:
// Verify setting BuildInParallel to true and StopOnFirstFailure to
// false will cause no change in BuildInParallel
msbuildTask.BuildInParallel = true;
msbuildTask.StopOnFirstFailure = false;
success = msbuildTask.Execute();
// Verify build did build second project which has the message SecondProject
mockEngine.AssertLogContains("SecondProject");
// Verify the correct msbuild task messages are in the log
Assert.True(msbuildTask.BuildInParallel); // "Iteration of 1 Expected BuildInParallel to be true"
break;
case 2:
// Verify setting BuildInParallel to false and StopOnFirstFailure to
// true will cause no change in BuildInParallel
msbuildTask.BuildInParallel = false;
msbuildTask.StopOnFirstFailure = true;
success = msbuildTask.Execute();
// Verify build did not build second project which has the message SecondProject
mockEngine.AssertLogDoesntContain("SecondProject");
// Verify the correct msbuild task messages are in the log
Assert.False(msbuildTask.BuildInParallel); // "Iteration of 2 Expected BuildInParallel to be false"
break;
case 3:
// Verify setting BuildInParallel to false and StopOnFirstFailure to
// false will cause no change in BuildInParallel
msbuildTask.BuildInParallel = false;
msbuildTask.StopOnFirstFailure = false;
success = msbuildTask.Execute();
// Verify build did build second project which has the message SecondProject
mockEngine.AssertLogContains("SecondProject");
// Verify the correct msbuild task messages are in the log
Assert.False(msbuildTask.BuildInParallel); // "Iteration of 3 Expected BuildInParallel to be false"
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
{
// Test the case where there is only one project and it has an error
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(project1)
};
MSBuild msbuildTask = new MSBuild();
MockEngine mockEngine = new MockEngine();
mockEngine.IsRunningMultipleNodes = true;
msbuildTask.BuildEngine = mockEngine;
msbuildTask.Projects = projects;
msbuildTask.Targets = new string[] { "msbuild" };
msbuildTask.BuildInParallel = false;
msbuildTask.StopOnFirstFailure = true;
bool success = msbuildTask.Execute();
Assert.False(success); // "Build Succeeded. See 'Standard Out' tab for details."
// Test the case where there are two projects and the last one has an error
projects = new ITaskItem[]
{
new TaskItem(project2), new TaskItem(project1)
};
msbuildTask = new MSBuild();
mockEngine = new MockEngine();
mockEngine.IsRunningMultipleNodes = true;
msbuildTask.BuildEngine = mockEngine;
msbuildTask.Projects = projects;
msbuildTask.Targets = new string[] { "msbuild" };
msbuildTask.BuildInParallel = false;
msbuildTask.StopOnFirstFailure = true;
success = msbuildTask.Execute();
Assert.False(success); // "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++)
{
// Test the case where the error is in the last target
MSBuild msbuildTask = new MSBuild();
MockEngine mockEngine = new MockEngine();
msbuildTask.BuildEngine = mockEngine;
msbuildTask.Projects = projects;
// Set to true as the expected result is false
bool success = true;
switch (i)
{
case 0:
// Test the case where the error is in the last project and RunEachTargetSeparately = true
msbuildTask.StopOnFirstFailure = true;
msbuildTask.RunEachTargetSeparately = true;
msbuildTask.Targets = new string[] { "T1", "T2", "T3" };
success = msbuildTask.Execute();
mockEngine.AssertLogContains("Proj2 T1 message");
mockEngine.AssertLogContains("Proj2 T2 message");
break;
case 1:
// Test the case where the error is in the second target out of 3.
msbuildTask.StopOnFirstFailure = true;
msbuildTask.RunEachTargetSeparately = true;
msbuildTask.Targets = new string[] { "T1", "T3", "T2" };
success = msbuildTask.Execute();
mockEngine.AssertLogContains("Proj2 T1 message");
mockEngine.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
msbuildTask.RunEachTargetSeparately = true;
msbuildTask.StopOnFirstFailure = false;
msbuildTask.Targets = new string[] { "T1", "T3", "T2" };
success = msbuildTask.Execute();
mockEngine.AssertLogContains("Proj2 T1 message");
mockEngine.AssertLogContains("Proj2 T2 message");
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
msbuildTask.StopOnFirstFailure = true;
msbuildTask.Targets = new string[] { "T1", "T2", "T3" };
success = msbuildTask.Execute();
mockEngine.AssertLogContains("Proj2 T1 message");
mockEngine.AssertLogContains("Proj2 T2 message");
// 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.
msbuildTask.StopOnFirstFailure = true;
msbuildTask.Targets = new string[] { "T1", "T3", "T2" };
success = msbuildTask.Execute();
mockEngine.AssertLogContains("Proj2 T1 message");
mockEngine.AssertLogDoesntContain("Proj2 T2 message");
// 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
msbuildTask.StopOnFirstFailure = false;
msbuildTask.Targets = new string[] { "T1", "T3", "T2" };
success = msbuildTask.Execute();
mockEngine.AssertLogContains("Proj2 T1 message");
mockEngine.AssertLogDoesntContain("Proj2 T2 message");
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>
");
try
{
ITaskItem[] projects = new ITaskItem[]
{
new TaskItem(projectFile2)
};
MSBuild msbuildTask = new MSBuild();
msbuildTask.BuildEngine = new MockEngine();
msbuildTask.Projects = projects;
bool success = msbuildTask.Execute();
Assert.True(success); // "Build failed. See 'Standard Out' tab for details."
Assert.Equal(5, msbuildTask.TargetOutputs.Length);
Assert.Equal("|a", msbuildTask.TargetOutputs[0].ItemSpec);
Assert.Equal("A|b", msbuildTask.TargetOutputs[1].ItemSpec);
Assert.Equal("B|c", msbuildTask.TargetOutputs[2].ItemSpec);
Assert.Equal("C|d", msbuildTask.TargetOutputs[3].ItemSpec);
Assert.Equal("D|", msbuildTask.TargetOutputs[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();
project.Build(logger);
logger.AssertLogContains("[foo.out]");
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
[Fact]
public void NonexistentTargetSkippedWhenSkipNonexistentTargetsSet()
{
ObjectModelHelpers.DeleteTempProjectDirectory();
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"SkipNonexistentTargets.csproj",
@"<Project>
<Target Name=`t` >
<MSBuild Projects=`SkipNonexistentTargets.csproj` Targets=`t_nonexistent;t2` SkipNonexistentTargets=`true` />
</Target>
<Target Name=`t2`> <Message Text=`t2 executing` /> </Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectSuccess(@"SkipNonexistentTargets.csproj", logger);
// Target t2 should still execute
logger.FullLog.ShouldContain("t2 executing");
logger.WarningCount.ShouldBe(0);
logger.ErrorCount.ShouldBe(0);
// t_missing target should be skipped
logger.FullLog.ShouldContain("Target \"t_nonexistent\" skipped");
}
[Fact]
public void NonexistentTargetFailsAfterSkipped()
{
ObjectModelHelpers.DeleteTempProjectDirectory();
ObjectModelHelpers.CreateFileInTempProjectDirectory(
"SkipNonexistentTargets.csproj",
@"<Project>
<Target Name=`t` >
<MSBuild Projects=`SkipNonexistentTargets.csproj` Targets=`t_nonexistent` SkipNonexistentTargets=`true` />
<MSBuild Projects=`SkipNonexistentTargets.csproj` Targets=`t_nonexistent` SkipNonexistentTargets=`false` />
</Target>
</Project>
");
MockLogger logger = new MockLogger(_testOutput);
ObjectModelHelpers.BuildTempProjectFileExpectFailure(@"SkipNonexistentTargets.csproj", logger);
logger.WarningCount.ShouldBe(0);
logger.ErrorCount.ShouldBe(1);
// t_missing target should be skipped
logger.FullLog.ShouldContain("Target \"t_nonexistent\" skipped");
// 2nd invocation of t_missing should fail the build resulting in target not found error (MSB4057)
logger.FullLog.ShouldContain("MSB4057");
}
[Fact]
public void MSBuildTaskPassesTaskIdToSpawnedBuilds()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project>
<Target Name=`Build`>
<Message Text=`test`/>
</Target>
</Project>");
string projectFile2 = ObjectModelHelpers.CreateTempFileOnDisk(@"
<Project>
<Target Name=`Build`>
<MSBuild Projects=`" + projectFile1 + @"` Targets=`Build` />
</Target>
</Project>");
try
{
Project project = new Project(projectFile2);
var logger = new MockLogger();
Assert.True(project.Build(logger));
var expectedTaskId = logger.TaskStartedEvents.First(t => t.TaskName == "MSBuild").BuildEventContext.TaskId;
var actualTaskId = logger.ProjectStartedEvents
.Where(p => p.ParentProjectBuildEventContext?.TaskId > 0)
.First()
.ParentProjectBuildEventContext.TaskId;
Assert.Equal(expectedTaskId, actualTaskId);
}
finally
{
File.Delete(projectFile1);
File.Delete(projectFile2);
}
}
[Fact]
public void CustomTaskWithBuildProjectFilePassesTaskId()
{
string projectFile1 = ObjectModelHelpers.CreateTempFileOnDisk($@"
<Project>
<UsingTask TaskName=`{nameof(BuildProjectFileTask)}` AssemblyFile =`{typeof(BuildProjectFileTask).Assembly.Location}` />
<Target Name=`Build`>
<{nameof(BuildProjectFileTask)} Project=`$(MSBuildThisFileFullPath)` Targets=`Other` />
</Target>
<Target Name=`Other`>
<Message Text=`test`/>
</Target>
</Project>");
try
{
Project project = new Project(projectFile1);
var logger = new MockLogger();
Assert.True(project.Build(logger));
var expectedTaskId = logger.TaskStartedEvents.First(t => t.TaskName == nameof(BuildProjectFileTask)).BuildEventContext.TaskId;
var actualTaskId = logger.ProjectStartedEvents
.Where(p => p.ParentProjectBuildEventContext?.TaskId > 0)
.First()
.ParentProjectBuildEventContext.TaskId;
Assert.Equal(expectedTaskId, actualTaskId);
}
finally
{
File.Delete(projectFile1);
}
}
}
public class BuildProjectFileTask : Task
{
public string Project { get; set; }
public string[] Targets { get; set; }
public override bool Execute()
{
this.BuildEngine.BuildProjectFile(Project, Targets, null, null);
return true;
}
}
}
|