-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implicit Sdk imports #1403
Implicit Sdk imports #1403
Conversation
fa5cd51
to
e6d4397
Compare
@@ -540,6 +545,15 @@ internal void AddToXml(ProjectElement child) | |||
} | |||
} | |||
|
|||
private static bool HasXmlRepresentation(ProjectElement element) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the IsImplicit
property should be enough, do you think we need a method to call instead of checking the property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It wasn't enough in the original PR because I only put Implicit
on ProjectImportElement
. But you're right: if I just extend it to all ProjectElement
s this becomes much cleaner.
private readonly string _name; | ||
private readonly string _originalValue; | ||
|
||
public TemporaryEnvironment(string name, string value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense for this to take an IDictionary<string, string>
so you could have one or more variables set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I envisioned a cascading set of usings but yeah, that's more elegant. Mind if we wait until after RC2?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave it up to you
@@ -83,6 +83,7 @@ public partial class ProjectExtensionsElement : Microsoft.Build.Construction.Pro | |||
public partial class ProjectImportElement : Microsoft.Build.Construction.ProjectElement | |||
{ | |||
internal ProjectImportElement() { } | |||
public bool Implicit { get { throw null; } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsImplicit
sounds better to me but I'll leave it up to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, looks like that matches existing ones better.
|
||
var sdksString = element.GetAttribute(XMakeAttributes.sdk); | ||
|
||
var sdks = sdksString.Contains(";") ? sdksString.Split(';') : new[] {sdksString}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use .Split(';', RemoveEmptyEntries).Select(i => i.Trim()).Where(i => !String.IsNullOrWhiteSpace())
to be as robust as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, absolutely! This file didn't use LINQ so I did it manually.
/// </summary> | ||
public ElementLocation ProjectLocation | ||
// TODO: *should* this be public? if it's not, you can't determine if an import is implicit from the public OM. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be public. With no public setter, it seems safe.
var implicitElements = | ||
xmlWithNoImplicits.SelectNodes($"//*[@{XMakeAttributes.@implicit}]"); | ||
|
||
if (implicitElements == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this ever happen? I think if the document is parsed and successfully cloned, this would always return a value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't think of a reason for this to fail either, but it seemed worth checking and throwing. Would you rather I just let a NRE happen later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did some searching and couldn't find an instance where it would return null unless the document couldn't be parsed. I think its safe to remove the check personally.
if (((XmlElement)child).GetAttribute(XMakeAttributes.@implicit).Length > 0) | ||
{ | ||
importTag = | ||
$" Import of \"{importProject}\" from Sdk \"{importSdk}\" was implied by the {XMakeElements.project} element's {XMakeAttributes.sdk} attribute."; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, the only thing I really want changed is for you to get a build error if you specify an SDK that doesn't exist, or is misspelled. I also think we may need to switch to handling the Sdk attribute in the evaluator rather than in the parser (for example, to handle dependencies between Sdks and importing the props/targets in the right order). However, let's go with what works for now for the first preview with this feature.
- Doesn't define a default location for the SDKs. MUST FIX
Do we actually need to fix this? My understanding is that we were going to set this for full framework MSBuild in its app.config file, and the .NET CLI would set the environment variable when it launched an MSBuild process.
- Allows
Sdk
attribute on any<Project>
element, even ones that are (recursively) imported. That's confusing and wrong.
Agreed that this should be disabled now, but I'm noodling on a design for handling Sdk dependencies where an Sdk's Sdk.props
file would list it's dependencies using the Sdk
attribute of the Project
element.
@@ -619,7 +633,11 @@ private void AddInitialChild(ProjectElement child) | |||
AddToXml(child); | |||
|
|||
_count++; | |||
MarkDirty("Add child element named '{0}'", child.ElementName); | |||
|
|||
if (HasXmlRepresentation(child)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't you also want to suppress marking the tree as dirty if the child is implicit in InsertBeforeChild
and InsertAfterChild
?
/// Location of the project attribute | ||
/// </summary> | ||
/// <remarks> | ||
/// For an implicit import, the location points to the Sdk attribute on the Project element. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line of the comment doesn't seem to match the behavior in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch--remnant of a previous attempt.
{ | ||
get { return XmlElement.GetAttributeLocation(XMakeAttributes.project); } | ||
get { return XmlElement.HasAttribute(XMakeAttributes.@implicit); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we represent whether an import is implicit or not with a field in this class instead of a separate attribute? What happens if someone actually uses the attribute?
<Import Project="foo.targets" _Implicit="Foo" />
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It throws an error: https://github.com/Microsoft/msbuild/pull/1403/files#diff-a9d9d41d4ebe72ee0285850966f63d3fR576
Test TK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I tried using a field in this class, but it caused problems with the preprocessor, since it walks only the XML tree.
@@ -1714,8 +1714,23 @@ public void Save(Encoding saveEncoding) | |||
{ | |||
using (ProjectWriter projectWriter = new ProjectWriter(_projectFileLocation.File, saveEncoding)) | |||
{ | |||
projectWriter.Initialize(XmlDocument); | |||
XmlDocument.Save(projectWriter); | |||
var xmlWithNoImplicits = (XmlDocument)XmlDocument.Clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At first glance the variable name led me to believe that it wouldn't have any implicit imports when it was cloned, not that you were going to remove them afterwards. It might be possible to make this clearer with a better name.
@@ -1081,7 +1081,6 @@ private void PerformDepthFirstPass(ProjectRootElement currentProjectOrImport) | |||
} | |||
|
|||
ProjectImportElement import = element as ProjectImportElement; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No-op whitespace change here.
var finalImportPath = Path.Combine(Environment.GetEnvironmentVariable("MSBuildSDKsPath"), | ||
sdkName, "Sdk", "Sdk.targets"); | ||
|
||
if (File.Exists(initialImportPath)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to generate an error if an Sdk is specified that is not found. However, that should be an error at evaluation, not parse time. To get that behavior for now, I think we should always add both implicit import elements whether the files exist or not, which will mean that all of our Sdks will need to have both Sdk.props
and Sdk.targets
.
When we add support for an Sdk
attribute on an Import
and implement the Project-level Sdk
attribute in terms of Sdk imports, we may be able to change how this works.
@@ -1,4 +1,4 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<?xml version="1.0" encoding="utf-8"?> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No-op changes (EOL probably) here and at end of file.
{ | ||
Directory.CreateDirectory(testSdkDirectory); | ||
|
||
string sdkPropsPath = Path.Combine(testSdkDirectory, "Sdk.props"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to be Path.Combine(testSdkDirectory, "Sdk", "Sdk.props")
to match the path where the parser is going to look for the Sdk imports.
@@ -45,53 +45,56 @@ public void ReadNone() | |||
public void ReadInvalidMissingProject() | |||
{ | |||
Assert.Throws<InvalidProjectFileException>(() => | |||
{ | |||
string content = @" | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of whitespace changes in this file.
File.WriteAllText(sdkPropsPath, string.Empty); | ||
File.WriteAllText(sdkTargetsPath, string.Empty); | ||
|
||
using (new Helpers.TemporaryEnvironment("MSBUILDMAGICIMPORTDIRECTORY", testSdkRoot)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to MSBuildSDKsPath
environment variable.
if (sdkName.Contains("/")) | ||
{ | ||
ProjectErrorUtilities.ThrowInvalidProject(element.GetAttributeLocation(XMakeAttributes.sdk), | ||
"InvalidSdkFormatTooManySlashes"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Self, actually add this string to the resx.
@@ -1714,8 +1714,18 @@ public void Save(Encoding saveEncoding) | |||
{ | |||
using (ProjectWriter projectWriter = new ProjectWriter(_projectFileLocation.File, saveEncoding)) | |||
{ | |||
projectWriter.Initialize(XmlDocument); | |||
XmlDocument.Save(projectWriter); | |||
var xmlWithNoImplicits = (XmlDocument)XmlDocument.Clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Clone()
method doesn't seem to be available in the .NET Core build. Probably the fix is to switch to CloneNode()
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, I apparently hadn't committed and pushed that fix when I made it. You're right--the replacement is CloneNode(true)
.
Consensus has emerged that the default value of the SDK location should be I need to:
|
@dotnet-bot test Windows_NT Build for Desktop please |
b3f2666
to
fb6455d
Compare
I'm going to |
…orts (it currently does)
5b5283b
to
7303fcd
Compare
@@ -40,9 +40,11 @@ internal static class XMakeAttributes | |||
internal const string taskName = "TaskName"; | |||
internal const string continueOnError = "ContinueOnError"; | |||
internal const string project = "Project"; | |||
internal const string @implicit = "_Implicit"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be isImplicit so you can get rid of the @
? Should the attribute name just be IsImplicit
?
7303fcd
to
681b98a
Compare
We expect to treat versions specified after a slash in the Sdk attribute as a "minimum supported version", but that is not fully designed yet. For the first iteration of Sdk support, ignore the version entirely, but allow it to be specified.
This better matches the MSBuild convention.
681b98a
to
9801c6d
Compare
@@ -1264,6 +1264,11 @@ internal static string[] CreateFiles(params string[] files) | |||
/// </summary> | |||
internal static string[] CreateFilesInDirectory(string rootDirectory, params string[] files) | |||
{ | |||
if (files == null) | |||
{ | |||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return null; [](start = 16, length = 12)
Return empty array so the upstream code can work without checking for null?
cc2122a
to
94214d3
Compare
@@ -57,6 +57,7 @@ protected override IEnumerable<ToolsetPropertyDefinition> ToolsVersions | |||
protected override IEnumerable<ToolsetPropertyDefinition> GetPropertyDefinitions(string toolsVersion) | |||
{ | |||
yield return new ToolsetPropertyDefinition(MSBuildConstants.ToolsPath, BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, _sourceLocation); | |||
yield return new ToolsetPropertyDefinition(MSBuildConstants.SdksPath, BuildEnvironmentHelper.Instance.MSBuildSDKsPath, _sourceLocation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this new ToolsetProperty read from anywhere? If it is, it looks like ToolsetConfigurationReader needs to be updated as well to support reading the SDK from the app.config too.
Apparently the local reader is only used on xplat as hardcoded values because there are no app configs on .net core.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not by MSBuild itself, but the first iteration of some of the magic SDKs plan to do SDK-to-SDK references using the property. It's set here for .NET Core and in app.config for full.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you may also have to change ToolsetConfigurationReader
to make the property flow from the app config too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, wait, no, that happens automatically, no need to change anything :)
|
||
var sdksString = element.GetAttribute(XMakeAttributes.sdk); | ||
|
||
var sdks = sdksString.Contains(";") ? sdksString.Split(';') : new[] {sdksString}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sdksString.Split(';') [](start = 54, length = 21)
You could also look into using ExpressionShredder.SplitSemiColonSeparatedList. Though it might be overkill for this situation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
worth thinking about for vnext
The if-exists checks meant that if you had a badly defined MSBuildSDKsPath or an SDK name that didn't exist, you would get no error and just silently get no imports. That's no good. This adds the implicit import unconditionally, requiring all SDKs to have both an Sdk.props and an Sdk.targets for the moment.
eec49d9
to
b3f22f8
Compare
Addressed the describe "only blocking issue"
First iteration of #1392.
Known issues:
Sdk
attribute on any<Project>
element, even ones that are (recursively) imported. That's confusing and wrong.Example of use:
test.proj
TestSdk\Sdk\Sdk.props
TestSdk\Sdk\Sdk.targets
preprocessor output: