diff --git a/ref/net46/Microsoft.Build/Microsoft.Build.cs b/ref/net46/Microsoft.Build/Microsoft.Build.cs index 0c901b5ba0e..9e45e466a86 100644 --- a/ref/net46/Microsoft.Build/Microsoft.Build.cs +++ b/ref/net46/Microsoft.Build/Microsoft.Build.cs @@ -90,10 +90,12 @@ public partial class ProjectImportElement : Microsoft.Build.Construction.Project { internal ProjectImportElement() { } public Microsoft.Build.Construction.ImplicitImportLocation ImplicitImportLocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string MinimumVersion { get { throw null; } set { } } public string Project { get { throw null; } set { } } public Microsoft.Build.Construction.ElementLocation ProjectLocation { get { throw null; } } public string Sdk { get { throw null; } set { } } public Microsoft.Build.Construction.ElementLocation SdkLocation { get { throw null; } } + public string Version { get { throw null; } set { } } protected override Microsoft.Build.Construction.ProjectElement CreateNewInstance(Microsoft.Build.Construction.ProjectRootElement owner) { throw null; } } [System.Diagnostics.DebuggerDisplayAttribute("#Imports={Count} Condition={Condition} Label={Label}")] diff --git a/ref/netstandard1.3/Microsoft.Build/Microsoft.Build.cs b/ref/netstandard1.3/Microsoft.Build/Microsoft.Build.cs index 1d02c2f2984..1cec1650f41 100644 --- a/ref/netstandard1.3/Microsoft.Build/Microsoft.Build.cs +++ b/ref/netstandard1.3/Microsoft.Build/Microsoft.Build.cs @@ -90,10 +90,12 @@ public partial class ProjectImportElement : Microsoft.Build.Construction.Project { internal ProjectImportElement() { } public Microsoft.Build.Construction.ImplicitImportLocation ImplicitImportLocation { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string MinimumVersion { get { throw null; } set { } } public string Project { get { throw null; } set { } } public Microsoft.Build.Construction.ElementLocation ProjectLocation { get { throw null; } } public string Sdk { get { throw null; } set { } } public Microsoft.Build.Construction.ElementLocation SdkLocation { get { throw null; } } + public string Version { get { throw null; } set { } } protected override Microsoft.Build.Construction.ProjectElement CreateNewInstance(Microsoft.Build.Construction.ProjectRootElement owner) { throw null; } } [System.Diagnostics.DebuggerDisplayAttribute("#Imports={Count} Condition={Condition} Label={Label}")] diff --git a/src/Build.OM.UnitTests/Construction/ProjectSdkImplicitImport_Tests.cs b/src/Build.OM.UnitTests/Construction/ProjectSdkImplicitImport_Tests.cs index c99a43537cf..75340899173 100644 --- a/src/Build.OM.UnitTests/Construction/ProjectSdkImplicitImport_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/ProjectSdkImplicitImport_Tests.cs @@ -20,6 +20,24 @@ namespace Microsoft.Build.UnitTests.OM.Construction /// public class ProjectSdkImplicitImport_Tests : IDisposable { + private const string ProjectTemplateSdkAsAttribute = @" + + {1} +"; + + private const string ProjectTemplateSdkAsElement = @" + + + {1} +"; + + private const string ProjectTemplateSdkAsExplicitImport = @" + + + {1} + +"; + private const string SdkName = "MSBuildUnitTestSdk"; private readonly string _testSdkRoot; private readonly string _testSdkDirectory; @@ -37,29 +55,18 @@ public ProjectSdkImplicitImport_Tests() } [Theory] - [InlineData(@" - - - null - - -")] - [InlineData(@" - - - - null - - -")] - public void SdkImportsAreInLogicalProject(string projectFormatString) + [InlineData(ProjectTemplateSdkAsAttribute, false)] + [InlineData(ProjectTemplateSdkAsElement, true)] + [InlineData(ProjectTemplateSdkAsExplicitImport, false)] + public void SdkImportsAreInLogicalProject(string projectFormatString, bool expectImportInLogicalProject) { + string projectInnerContents = @"null"; File.WriteAllText(_sdkPropsPath, "Hello"); File.WriteAllText(_sdkTargetsPath, "World"); using (new Helpers.TemporaryEnvironment("MSBuildSDKsPath", _testSdkRoot)) { - string content = string.Format(projectFormatString, SdkName); + string content = string.Format(projectFormatString, SdkName, projectInnerContents); ProjectRootElement projectRootElement = ProjectRootElement.Create(XmlReader.Create(new StringReader(content))); @@ -68,42 +75,30 @@ public void SdkImportsAreInLogicalProject(string projectFormatString) IList children = project.GetLogicalProject().ToList(); // style will have an extra ProjectElment. - var expected = projectFormatString.Contains("Sdk=") ? 6 : 7; - Assert.Equal(expected, children.Count); + Assert.Equal(expectImportInLogicalProject ? 7 : 6, children.Count); } } [Theory] - [InlineData(@" - - - null - - -")] - [InlineData(@" - - - - null - - -")] - public void SdkImportsAreInImportList(string projectFormatString) + [InlineData(ProjectTemplateSdkAsAttribute, false)] + [InlineData(ProjectTemplateSdkAsElement, false)] + [InlineData(ProjectTemplateSdkAsExplicitImport, true)] + public void SdkImportsAreInImportList(string projectFormatString, bool expectImportInLogicalProject) { + string projectInnerContents = @"null"; File.WriteAllText(_sdkPropsPath, "Hello"); File.WriteAllText(_sdkTargetsPath, "World"); using (new Helpers.TemporaryEnvironment("MSBuildSDKsPath", _testSdkRoot)) { - string content = string.Format(projectFormatString, SdkName); + string content = string.Format(projectFormatString, SdkName, projectInnerContents); ProjectRootElement projectRootElement = ProjectRootElement.Create(XmlReader.Create(new StringReader(content))); var project = new Project(projectRootElement); - // The XML representation of the project should indicate there are no imports - Assert.Equal(0, projectRootElement.Imports.Count); + // The XML representation of the project should only indicate an import if they are not implicit. + Assert.Equal(expectImportInLogicalProject ? 2 : 0, projectRootElement.Imports.Count); // The project representation should have imports Assert.Equal(2, project.Imports.Count); @@ -128,14 +123,24 @@ public void SdkImportsAreInImportList(string projectFormatString) [Theory] [InlineData(@" -")] +", false)] [InlineData(@" -")] - public void SdkSupportsMultiple(string projectFormatString) +", false)] + [InlineData(@" + + + + + + + + +", true)] + public void SdkSupportsMultiple(string projectFormatString, bool expectImportInLogicalProject) { IList sdkNames = new List { @@ -161,7 +166,7 @@ public void SdkSupportsMultiple(string projectFormatString) Project project = new Project(projectRootElement); // The XML representation of the project should indicate there are no imports - Assert.Equal(0, projectRootElement.Imports.Count); + Assert.Equal(expectImportInLogicalProject ? 6 : 0, projectRootElement.Imports.Count); // The project representation should have twice as many imports as SDKs Assert.Equal(sdkNames.Count * 2, project.Imports.Count); @@ -173,21 +178,12 @@ public void SdkSupportsMultiple(string projectFormatString) } [Theory] - [InlineData(@" -")] - [InlineData(@" - -")] - public void ProjectWithSdkImportsIsCloneable(string projectFileFirstLineFormat) + [InlineData(ProjectTemplateSdkAsAttribute)] + [InlineData(ProjectTemplateSdkAsElement)] + [InlineData(ProjectTemplateSdkAsExplicitImport)] + public void ProjectWithSdkImportsIsCloneable(string projectFormatString) { - File.WriteAllText(_sdkPropsPath, ""); - File.WriteAllText(_sdkTargetsPath, ""); - - using (new Helpers.TemporaryEnvironment("MSBuildSDKsPath", _testSdkRoot)) - { - // Based on the new-console-project CLI template (but not matching exactly - // should not be a deal-breaker). - string content = $@"{string.Format(projectFileFirstLineFormat, SdkName)} + string projectInnerContents = @" Exe netcoreapp1.0 @@ -200,10 +196,15 @@ public void ProjectWithSdkImportsIsCloneable(string projectFileFirstLineFormat) - - -"; + "; + File.WriteAllText(_sdkPropsPath, " < Project />"); + File.WriteAllText(_sdkTargetsPath, ""); + using (new Helpers.TemporaryEnvironment("MSBuildSDKsPath", _testSdkRoot)) + { + // Based on the new-console-project CLI template (but not matching exactly + // should not be a deal-breaker). + string content = string.Format(projectFormatString, SdkName, projectInnerContents); ProjectRootElement project = ProjectRootElement.Create(XmlReader.Create(new StringReader(content))); project.DeepClone(); @@ -211,21 +212,12 @@ public void ProjectWithSdkImportsIsCloneable(string projectFileFirstLineFormat) } [Theory] - [InlineData(@" -")] - [InlineData(@" - -")] - public void ProjectWithSdkImportsIsRemoveable(string projectFileFirstLineFormat) + [InlineData(ProjectTemplateSdkAsAttribute)] + [InlineData(ProjectTemplateSdkAsElement)] + [InlineData(ProjectTemplateSdkAsExplicitImport)] + public void ProjectWithSdkImportsIsRemoveable(string projectFormatString) { - File.WriteAllText(_sdkPropsPath, ""); - File.WriteAllText(_sdkTargetsPath, ""); - - using (new Helpers.TemporaryEnvironment("MSBuildSDKsPath", _testSdkRoot)) - { - // Based on the new-console-project CLI template (but not matching exactly - // should not be a deal-breaker). - string content = $@"{string.Format(projectFileFirstLineFormat, SdkName)} + string projectInnerContents = @" Exe netcoreapp1.0 @@ -238,10 +230,15 @@ public void ProjectWithSdkImportsIsRemoveable(string projectFileFirstLineFormat) - - -"; + "; + File.WriteAllText(_sdkPropsPath, " < Project />"); + File.WriteAllText(_sdkTargetsPath, ""); + using (new Helpers.TemporaryEnvironment("MSBuildSDKsPath", _testSdkRoot)) + { + // Based on the new-console-project CLI template (but not matching exactly + // should not be a deal-breaker). + string content = string.Format(projectFormatString, SdkName, projectInnerContents); ProjectRootElement project = ProjectRootElement.Create(XmlReader.Create(new StringReader(content))); ProjectRootElement clone = ProjectRootElement.Create(XmlReader.Create(new StringReader(content))); @@ -280,21 +277,28 @@ public void ProjectWithInvalidSdkName() /// /// Verifies that an empty SDK attribute works and nothing is imported. /// - [Fact] - public void ProjectWithEmptySdkName() + [Theory] + [InlineData(ProjectTemplateSdkAsAttribute, false)] + [InlineData(ProjectTemplateSdkAsElement, true)] + [InlineData(ProjectTemplateSdkAsExplicitImport, true)] + public void ProjectWithEmptySdkName(string projectFormatString, bool throwsOnEvaluate) { + string projectInnerContents = + @"null"; + using (new Helpers.TemporaryEnvironment("MSBuildSDKsPath", _testSdkRoot)) { - string content = @" - - - null - - "; - - Project project = new Project(ProjectRootElement.Create(XmlReader.Create(new StringReader(content)))); - - Assert.Equal(0, project.Imports.Count); + string content = string.Format(projectFormatString, string.Empty, projectInnerContents); + if (throwsOnEvaluate) + { + Assert.Throws( + () => new Project(ProjectRootElement.Create(XmlReader.Create(new StringReader(content))))); + } + else + { + var project = new Project(ProjectRootElement.Create(XmlReader.Create(new StringReader(content)))); + Assert.Equal(0, project.Imports.Count); + } } } diff --git a/src/Build/Construction/ProjectImportElement.cs b/src/Build/Construction/ProjectImportElement.cs index aa9ffd01570..8d83b856e44 100644 --- a/src/Build/Construction/ProjectImportElement.cs +++ b/src/Build/Construction/ProjectImportElement.cs @@ -22,10 +22,11 @@ public class ProjectImportElement : ProjectElement /// /// Initialize a parented ProjectImportElement /// - internal ProjectImportElement(XmlElementWithLocation xmlElement, ProjectElementContainer parent, ProjectRootElement containingProject) + internal ProjectImportElement(XmlElementWithLocation xmlElement, ProjectElementContainer parent, ProjectRootElement containingProject, SdkReference sdkReference = null) : base(xmlElement, parent, containingProject) { ErrorUtilities.VerifyThrowArgumentNull(parent, "parent"); + ParsedSdkReference = sdkReference; } /// @@ -75,12 +76,40 @@ public string Sdk set { ErrorUtilities.VerifyThrowArgumentLength(value, XMakeAttributes.sdk); - + if (!CheckUpdatedSdk()) return; ProjectXmlUtilities.SetOrRemoveAttribute(XmlElement, XMakeAttributes.sdk, value); MarkDirty("Set Import Sdk {0}", value); } } + /// + /// Gets or sets the version associated with this SDK import + /// + public string Version + { + get { return ProjectXmlUtilities.GetAttributeValue(XmlElement, XMakeAttributes.sdkVersion); } + set + { + if (!CheckUpdatedSdk()) return; + ProjectXmlUtilities.SetOrRemoveAttribute(XmlElement, XMakeAttributes.sdkVersion, value); + MarkDirty("Set Import Version {0}", value); + } + } + + /// + /// Gets or sets the minimum SDK version required by this import. + /// + public string MinimumVersion + { + get { return ProjectXmlUtilities.GetAttributeValue(XmlElement, XMakeAttributes.sdkMinimumVersion); } + set + { + if (!CheckUpdatedSdk()) return; + ProjectXmlUtilities.SetOrRemoveAttribute(XmlElement, XMakeAttributes.sdkMinimumVersion, value); + MarkDirty("Set Import Minimum Version {0}", value); + } + } + /// /// Location of the Sdk attribute /// @@ -139,5 +168,22 @@ protected override ProjectElement CreateNewInstance(ProjectRootElement owner) { return owner.CreateImportElement(this.Project); } + + private bool CheckUpdatedSdk() + { + + SdkReference sdk = new SdkReference( + ProjectXmlUtilities.GetAttributeValue(XmlElement, XMakeAttributes.sdk, true), + ProjectXmlUtilities.GetAttributeValue(XmlElement, XMakeAttributes.sdkVersion, true), + ProjectXmlUtilities.GetAttributeValue(XmlElement, XMakeAttributes.sdkMinimumVersion, true)); + + if (sdk.Equals(ParsedSdkReference)) + { + return false; + } + + ParsedSdkReference = sdk; + return true; + } } } diff --git a/src/Build/Evaluation/ProjectParser.cs b/src/Build/Evaluation/ProjectParser.cs index 9f81b243933..c08247afe34 100644 --- a/src/Build/Evaluation/ProjectParser.cs +++ b/src/Build/Evaluation/ProjectParser.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using Microsoft.Build.Framework; #if (!STANDALONEBUILD) using Microsoft.Internal.Performance; @@ -515,12 +516,19 @@ private ProjectImportElement ParseProjectImportElement(XmlElementWithLocation el ); ProjectXmlUtilities.VerifyThrowProjectAttributes(element, s_validAttributesOnImport); - ProjectXmlUtilities.VerifyThrowProjectRequiredAttribute(element, XMakeAttributes.project); - ProjectXmlUtilities.VerifyThrowProjectNoChildElements(element); - return new ProjectImportElement(element, parent, _project); + SdkReference sdk = null; + if (element.HasAttribute(XMakeAttributes.sdk)) + { + sdk = new SdkReference( + element.GetAttribute(XMakeAttributes.sdk), + element.GetAttribute(XMakeAttributes.sdkVersion), + element.GetAttribute(XMakeAttributes.sdkMinimumVersion)); + } + + return new ProjectImportElement(element, parent, _project, sdk); } ///