Skip to content

Commit

Permalink
Check schema when performing a sln build. (#2593)
Browse files Browse the repository at this point in the history
* Check that the schema matches either empty or the MSBuild default schema
when building a solution file.

* Special case .rptproj files. This file format looks like a standard
MSBuild file but specifies ToolsVersion=2.0 and a default XML schema.
Since the XML parser treats that schema as equivalent to empty and empty
is now allowed, there's no good way to determine if it's an MSBuild file
until we try to parse. This results in an error in MSBuild 15 rather
than a warning in MSBuild 14 for solutions with a .rptproj file. To
solve this, the sln build will now allow projects where the schema is
the default MSBuild schema or empty and ToolsVersion is not 2.0.

* Special case to detect .dwproj in sln based on XML namespace
declarations in the Project element.
  • Loading branch information
AndyGerlicher authored Jan 4, 2018
1 parent 58ed985 commit d026eba
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 5 deletions.
55 changes: 52 additions & 3 deletions src/Build.UnitTests/Construction/SolutionFile_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
using System.IO;

using Microsoft.Build.Construction;
using Microsoft.Build.Engine.UnitTests;
using Microsoft.Build.Shared;



using Shouldly;
using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
using Xunit;

Expand Down Expand Up @@ -259,6 +258,56 @@ public void CanBeMSBuildFile()
}
}

/// <summary>
/// Test CanBeMSBuildFile
/// </summary>
[Fact]
public void CanBeMSBuildFileRejectsMSBuildLikeFiles()
{
using (var env = TestEnvironment.Create())
{
string rptprojProjContent = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Project xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" ToolsVersion=""2.0"">
<DataSources />
<Reports />
</Project>";
string dwprojProjContent = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Project xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:ddl2=""http://schemas.microsoft.com/analysisservices/2003/engine/2"" xmlns:ddl2_2=""http://schemas.microsoft.com/analysisservices/2003/engine/2/2"" xmlns:ddl100_100=""http://schemas.microsoft.com/analysisservices/2008/engine/100/100"" xmlns:ddl200=""http://schemas.microsoft.com/analysisservices/2010/engine/200"" xmlns:ddl200_200=""http://schemas.microsoft.com/analysisservices/2010/engine/200/200"" xmlns:dwd=""http://schemas.microsoft.com/DataWarehouse/Designer/1.0"">
<ProductVersion />
<SchemaVersion />
<State />
<Database />
<Cubes />
</Project>";

string rptprojPath = env.CreateFile(".rptproj").Path;
File.WriteAllText(rptprojPath, rptprojProjContent);
string dqprojPath = env.CreateFile(".dwproj").Path;
File.WriteAllText(dqprojPath, dwprojProjContent);

// Create the SolutionFile object
string solutionFileContents =
@"
Microsoft Visual Studio Solution File, Format Version 8.00
Project('{F14B399A-7131-4C87-9E4B-1186C45EF12D}') = 'PrtProj', '" + Path.GetFileName(rptprojPath) + @"', '{CCCCCCCC-9925-4D57-9DAF-E0A9D936ABDB}'
ProjectSection(ProjectDependencies) = postProject
EndProjectSection
EndProject
Project('{D2ABAB84-BF74-430A-B69E-9DC6D40DDA17}') = 'DwProj', '" + Path.GetFileName(dqprojPath) + @"', '{DEA89696-F42B-4B58-B7EE-017FF40817D1}'
ProjectSection(ProjectDependencies) = postProject
EndProjectSection
EndProject";

string error = null;
SolutionFile solution = ParseSolutionHelper(solutionFileContents);
ProjectInSolution project1 = solution.ProjectsByGuid["{CCCCCCCC-9925-4D57-9DAF-E0A9D936ABDB}"];
ProjectInSolution project2 = solution.ProjectsByGuid["{DEA89696-F42B-4B58-B7EE-017FF40817D1}"];

project1.CanBeMSBuildProjectFile(out error).ShouldBe(false);
project2.CanBeMSBuildProjectFile(out error).ShouldBe(false);
}
}

/// <summary>
/// Test ParseEtpProject function.
/// </summary>
Expand Down
46 changes: 44 additions & 2 deletions src/Build/Construction/Solution/ProjectInSolution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using BuildEventFileInfo = Microsoft.Build.Shared.BuildEventFileInfo;
using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities;
using System.Collections.ObjectModel;
using System.Linq;

namespace Microsoft.Build.Construction
{
Expand Down Expand Up @@ -323,8 +324,30 @@ internal bool CanBeMSBuildProjectFile(out string errorMessage)

if (mainProjectElement != null && mainProjectElement.LocalName == "Project")
{
_canBeMSBuildProjectFile = true;
return _canBeMSBuildProjectFile;
// MSBuild supports project files with an empty (supported in Visual Studio 2017) or the default MSBuild
// namespace.
bool emptyNamespace = string.IsNullOrEmpty(mainProjectElement.NamespaceURI);
bool defaultNamespace = String.Compare(mainProjectElement.NamespaceURI,
XMakeAttributes.defaultXmlNamespace,
StringComparison.OrdinalIgnoreCase) == 0;
bool projectElementInvalid = ElementContainsInvalidNamespaceDefitions(mainProjectElement);

// If the MSBuild namespace is declared, it is very likely an MSBuild project that should be built.
if (defaultNamespace)
{
_canBeMSBuildProjectFile = true;
return _canBeMSBuildProjectFile;
}

// This is a bit of a special case, but an rptproj file will contain a Project with no schema that is
// not an MSBuild file. It will however have ToolsVersion="2.0" which is not supported with an empty
// schema. This is not a great solution, but it should cover the customer reported issue. See:
// https://github.com/Microsoft/msbuild/issues/2064
if (emptyNamespace && !projectElementInvalid && mainProjectElement.GetAttribute("ToolsVersion") != "2.0")
{
_canBeMSBuildProjectFile = true;
return _canBeMSBuildProjectFile;
}
}
}
// catch all sorts of exceptions - if we encounter any problems here, we just assume the project file is not
Expand Down Expand Up @@ -463,6 +486,25 @@ static internal string DisambiguateProjectTargetName(string uniqueProjectName)
return uniqueProjectName;
}

/// <summary>
/// Check a Project element for known invalid namespace definitions.
/// </summary>
/// <param name="mainProjectElement">Project XML Element</param>
/// <returns>True if the element contains known invalid namespace definitions</returns>
private static bool ElementContainsInvalidNamespaceDefitions(XmlElement mainProjectElement)
{
if (mainProjectElement.HasAttributes)
{
// Data warehouse projects (.dwproj) will contain a Project element but are invalid MSBuild. Check attributes
// on Project for signs that this is a .dwproj file. If there are, it's not a valid MSBuild file.
return mainProjectElement.Attributes.OfType<XmlAttribute>().Any(a =>
a.Name.Equals("xmlns:dwd", StringComparison.OrdinalIgnoreCase) ||
a.Name.StartsWith("xmlns:dd", StringComparison.OrdinalIgnoreCase));
}

return false;
}

#endregion

#region Constants
Expand Down

0 comments on commit d026eba

Please sign in to comment.