Skip to content

Allow msbuild to pass properties and metadata as analyzerconfig:#43617

Merged
chsienki merged 4 commits intodotnet:masterfrom
chsienki:msbuild_to_editorconfig
May 6, 2020
Merged

Allow msbuild to pass properties and metadata as analyzerconfig:#43617
chsienki merged 4 commits intodotnet:masterfrom
chsienki:msbuild_to_editorconfig

Conversation

@chsienki
Copy link
Member

  • Add new target to evaluate requested properties and metadata
  • Add new task to turn collected evaluations into a global analyzer config
  • Add tests

This PR aims to answer the question of how analyzers / generators get information from MSBuild.

This implements the MSBuild -> Editorconfig portion. Consuming the generated editorconfig will be done in a different PR.

The basic idea is that an analyzer/generator can declare the properties and metadata it is interested in via an item group in a targets file (added via the usual nuget mechanism):

<ItemGroup> 
  <!-- Properties -->
  <AnalyzerProperty Include="PropertyName" />
 
  <!-- Metadata -->
  <AnalyzerItemMetadata Include="ItemType" MetadataName="MetadataName" />
</ItemGroup>

For instance, to access the TargetFramework and LangVersion properties:

<ItemGroup> 
  <AnalyzerProperty Include="TargetFramework" />
  <AnalyzerProperty Include="LangVersion" />
</ItemGroup>

Or to access the %(AccessedTime) of @(Compile) items:

<ItemGroup> 
  <AnalyzerItemMetadata Include="Compile" MetadataName="AccessedTime" />
</ItemGroup>

AnalyzerProperty items end up the in the global section of the generated config. Metadata items are placed in merged sections based on the full path of the ItemSpec, with a combination of the ItemType that produced it and the name of the metadata.

Eg:

<ItemGroup> 
  <AnalyzerProperty Include="TargetFramework" />
  <AnalyzerItemMetadata Include="Compile" MetadataName="AccessedTime" />
  <AnalyzerItemMetadata Include="AdditionalFiles" MetadataName="ModifiedTime" />
</ItemGroup>

For a project like:

<Project Sdk='Microsoft.NET.Sdk'>
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  
  <ItemGroup>
    <Compile Include="file1.cs" />
    <Compile Include="file2.cs" />
    <AdditionalFiles Include="file1.cs" />
  </ItemGroup>
</Project>

Would produce an editor config like:

is_global = true
msbuild_property.TargetFramework = netstandard2.0

[c:\full\path\to\file1.cs]
msbuild_item.Compile.AccessedTime = 2004-08-14 16:52:36.3168743
msbuild_item.AdditionalFiles.ModifiedTime = 2004-08-14 16:52:36.3168743

[c:\full\path\to\file2.cs]
msbuild_item.Compile.AccessedTime = 2004-08-14 16:52:36.3168743

- Add new target to evaluate requested properties and metadata
- Add new task to turn collected evaluations into a global analyzer config
- Add tests
@chsienki chsienki requested a review from a team as a code owner April 23, 2020 20:32
@chsienki
Copy link
Member Author

Open issue:

We use the %(FullPath) of the ItemSpec so that we don't have to worry about the location of the editorconfig. For items that don't map to an item on disk, this ends up a combination the directory the target is in and the item name.

Is that actually a problem? The retrieval mechanism in the compiler requires a file path to ask about, so it doesn't make any sense to have metadata about non-items on disk. Should we block this at the target level?

I considered filtering based on existence, but it seems possible that a file could get dropped on disk between the target and compilation so that the file would exist by the time lookup occured.

@chsienki
Copy link
Member Author

chsienki commented Apr 23, 2020

@rainersigwald and @dsplaisted Could you check the MSBuild foo over for me? Happy to walk over it in a call or can send binary logs that show how it runs.

/cc @agocke @mavasani First half of #42219

Copy link
Member

@dsplaisted dsplaisted left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, though I'd like @rainersigwald to take a look too.

Also it would be better to have tests that exercise the logic in the .targets file too. I'm not sure how hard that is with your infrastructure.

<Target Name="GenerateMSBuildAnalyzerConfigFile"
BeforeTargets="BeforeCompile;CoreCompile"
DependsOnTargets="PrepareForBuild;CoreGenerateAssemblyInfo"
Condition="'$(_GeneratedAnalyzerConfigShouldRun)' == 'true'"
Copy link
Member

@rainersigwald rainersigwald Apr 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think your ordering around this will work--did you validate that the no-items-defined case skips this target?

Generally the condition is evaluated before DependsOn or BeforeTargets run. It doesn't stop BeforeTargets from running . . . but it checks before running them. https://docs.microsoft.com/en-us/visualstudio/msbuild/target-build-order?view=vs-2019#determine-the-target-build-order

<Project>
  <PropertyGroup>
    <Condition>true</Condition>
  </PropertyGroup>

  <Target Name="entry" Condition="$(Condition) == true" />

  <Target Name="before" BeforeTargets="entry">
    <PropertyGroup>
      <Condition>false</Condition>
    </PropertyGroup>
  </Target>
</Project>
$ msbuild -v:d .\foo.proj
Microsoft (R) Build Engine version 16.7.0-dev-20221-01+73c84c6bd for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 4/23/2020 4:22:53 PM.
     0>Process = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\MSBuild\Current\Bin\MSBuild.exe"
       MSBuild executable path = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\MSBuild\Current\Bin\MSBuild.exe"
       Command line arguments = ""C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\\MSBuild\Current\Bin\MSBuild.exe" -v:d .\foo.proj"
       Current directory = "S:\msbuild"
       MSBuild version = "16.7.0-dev-20221-01+73c84c6bd"
     1>Project "S:\msbuild\foo.proj" on node 1 (default targets).
     1>Building with tools version "Current".
     1>Target "before" in project "S:\msbuild\foo.proj" (target "entry" depends on it):
     1>Done building target "before" in project "foo.proj".
     1>Target "entry" in project "S:\msbuild\foo.proj" (entry point):
     1>Done building target "entry" in project "foo.proj".
     1>Done Building Project "S:\msbuild\foo.proj" (default targets).

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.23

Instead, you can order via DependsOn:

<Project>
  <PropertyGroup>
    <Condition>true</Condition>
  </PropertyGroup>

  <Target Name="OrderingOnly" DependsOnTargets="SetCondition;DoWork" />

  <Target Name="DoWork" Condition="$(Condition) == true" />

  <Target Name="SetCondition"><!-- Must run before DoWork -->
    <PropertyGroup>
      <Condition>false</Condition>
    </PropertyGroup>
  </Target>
</Project>
$ msbuild -v:d .\bar.proj
Microsoft (R) Build Engine version 16.7.0-dev-20221-01+73c84c6bd for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 4/23/2020 4:24:02 PM.
     0>Process = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\MSBuild\Current\Bin\MSBuild.exe"
       MSBuild executable path = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\MSBuild\Current\Bin\MSBuild.exe"
       Command line arguments = ""C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\\MSBuild\Current\Bin\MSBuild.exe" -v:d .\bar.proj"
       Current directory = "S:\msbuild"
       MSBuild version = "16.7.0-dev-20221-01+73c84c6bd"
     1>Project "S:\msbuild\bar.proj" on node 1 (default targets).
     1>Building with tools version "Current".
     1>Target "SetCondition" in project "S:\msbuild\bar.proj" (target "OrderingOnly" depends on it):
     1>Done building target "SetCondition" in project "bar.proj".
       Target "DoWork" skipped, due to false condition; ($(Condition) == true) was evaluated as (false == true).
     1>Target "OrderingOnly" in project "S:\msbuild\bar.proj" (entry point):
     1>Done building target "OrderingOnly" in project "bar.proj".
     1>Done Building Project "S:\msbuild\bar.proj" (default targets).

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.24

#Resolved

Copy link
Member Author

@chsienki chsienki Apr 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I did try and it seems to work as expected, but it took a few iterations/variations. Using a root target that depends on both seems better. #Resolved

</_GeneratedAnalyzerConfigItem>

<!-- Transform the collected items into their full path -->
<_GeneratedAnalyzerConfigMetadata Include="@(_GeneratedAnalyzerConfigItem->'%(FullPath)')" />
Copy link
Member

@rainersigwald rainersigwald Apr 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this better than extracting FullPath inside your task? #Resolved

Copy link
Member Author

@chsienki chsienki Apr 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was originally trying to do as much as possible outside the task, but yeah, it seems like given we're always going to run the task always anyway it would be more efficient to handle it in there. #Resolved

</ItemGroup>

<!-- Transform the collected properties and items into an editor config file -->
<GenerateMSBuildAnalyzerConfig
Copy link
Member

@rainersigwald rainersigwald Apr 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love that this is unconditional, but it's pretty simple and I can't think of a better way to do it. #Resolved

- Fix target ordering
- Do full path in task
- Add full path tests
@chsienki
Copy link
Member Author

@dotnet/roslyn-compiler for compiler side review please :)


namespace Microsoft.CodeAnalysis.BuildTasks
{
public class GenerateMSBuildAnalyzerConfig : Task
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenerateMSBuildAnalyzerConfig [](start = 17, length = 29)

I would be great to capture the documentation you wrote in OP description here. #Closed

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.CodeAnalysis.BuildTasks
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namespace [](start = 0, length = 9)

nit: #nullable enable? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nullable is enabled at the project level for this assembly. Stylistically, are we still putting in source?


In reply to: 419572135 [](ancestors = 419572135)

Copy link
Member

@jaredpar jaredpar May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, didn't realize we'd done that. #Closed

Copy link
Member

@jcouv jcouv May 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Project-level sounds good to me. Thanks #Closed

builder.Append("msbuild_property.")
.Append(prop.ItemSpec)
.Append(" = ")
.AppendLine(prop.GetMetadata("Value")); //TODO: do we need to encode/escape this?
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO [](start = 65, length = 4)

I agree that there is an encoding/escaping question here (and other concats below). But we don't add TODO markers in the code. Some options: (1) we add encoding, or (2) add a check (fail gracefully), or (3) create a tracking issue (link here). #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I didn't notice that was still even in there. I'll write some tests to verify the behavior and either fix or open an issue.


In reply to: 419574615 [](ancestors = 419574615)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #43970 to track, and added a test that covers it.


In reply to: 419782382 [](ancestors = 419782382,419574615)

Requested properties/items are requested via item groups like:

<AnalyzerProperty Include="PropertyNameToEval" />
<AnalyzerItemMetadata Include="ItemType" MetadataName="MetaDataToRetrieve" />
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MetaData [](start = 61, length = 8)

nit: "Metadata" #Closed

@agocke
Copy link
Member

agocke commented May 4, 2020

@chsienki The editorconfig spec says that all directory separators in section names must be forward slashes. I think we should abide by that, even though using absolute paths is technically illegal, just to prevent other parsers potentially falling over. #Resolved


<Target Name="GenerateMSBuildAnalyzerConfigFileShouldRun"
BeforeTargets="GenerateMSBuildAnalyzerConfigFile"
Condition="'$(Language)'=='VB' or '$(Language)'=='C#'">
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, why do we have these language checks? Would F# potentially also want to benefit from generating such editorconfig files, by re-using our task and/or targets? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good point, I'm not sure that we actually need them.


In reply to: 419677677 [](ancestors = 419677677)

</PropertyGroup>

<Target Name="GenerateMSBuildAnalyzerConfigFileShouldRun"
BeforeTargets="GenerateMSBuildAnalyzerConfigFile"
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BeforeTargets [](start = 10, length = 13)

Sorry if this is a basic question, but do we need this BeforeTargets given that GenerateMSBuildAnalyzerConfigFile lists this target (GenerateMSBuildAnalyzerConfigFileShouldRun) in its DependsOnTargets list already? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, just a hangover from the first commit. Fixed.


In reply to: 419679214 [](ancestors = 419679214)

</PropertyGroup>
</Target>

<Target Name="GenerateMSBuildAnalyzerConfigFile"
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenerateMSBuildAnalyzerConfigFile [](start = 16, length = 33)

The name of this target seems confusing, since it's also used as a property above. Or is this dual meaning how things are typically done in MSBuild? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, its weird, but there is precedent in other targets to have a target/property named the same thing.


In reply to: 419682753 [](ancestors = 419682753)


<!-- Record that we'll write a file, and add it to the editorconfig inputs -->
<FileWrites Include="$(GeneratedMSBuildAnalyzerConfigFile)" />
<EditorConfigFiles Include="$(GeneratedMSBuildAnalyzerConfigFile)" />
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/> [](start = 74, length = 2)

formatting nit: double space before #Closed

DependsOnTargets="PrepareForBuild;GenerateMSBuildAnalyzerConfigFileShouldRun;GenerateMSBuildAnalyzerConfigFileCore"
Condition="'$(Language)'=='VB' or '$(Language)'=='C#'" />

<Target Name="GenerateMSBuildAnalyzerConfigFileShouldRun">
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenerateMSBuildAnalyzerConfigFileShouldRun [](start = 16, length = 42)

It seems we have two targets named GenerateMSBuildAnalyzerConfigFileShouldRun with similar definitions. Is that intentional? Not sure how that works #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy paste mistake. (the second one just overrides the first so works the same). Fixed.


In reply to: 419693689 [](ancestors = 419693689)

[Fact]
public void DuplicateItemSpecsAreCombinedInSections()
{
TaskItem item1 = new TaskItem("c:\\file1.cs", new Dictionary<string, string> { { "ItemType", "Compile" }, { "MetadataName", "ToRetrieve" }, { "ToRetrieve", "abc123" } });
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MetadataName [](start = 121, length = 12)

Just to confirm, "ItemType" and "MetadataName" are handled/provided by MSBuild infrastructure, so we don't have to worry about casing variations, correct? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


In reply to: 419696372 [](ancestors = 419696372)

<EditorConfigFiles Include="@(PotentialEditorConfigFiles->Exists())" Condition="'$(DiscoverEditorConfigFiles)' != 'false'" />
</ItemGroup>

<!--
Copy link
Member

@jcouv jcouv May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested with a test project, lots of combinations of values and the msbuild log viewer. Basically copy the updated targets into a directory.build.targets, and change the task assembly path to the Roslyn source version. We should definitely think about investing in some sort of structured tests for these files.


In reply to: 419698161 [](ancestors = 419698161)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with review pass (iteration 2)

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.CodeAnalysis.BuildTasks
Copy link
Member

@jaredpar jaredpar May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, didn't realize we'd done that. #Closed


namespace Microsoft.CodeAnalysis.BuildTasks
{
public class GenerateMSBuildAnalyzerConfig : Task
Copy link
Member

@jaredpar jaredpar May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class GenerateMSBuildAnalyzerConfig : Task
public sealed class GenerateMSBuildAnalyzerConfig : Task
``` #Resolved

Comment on lines +26 to +27
MetadataItems = new ITaskItem[0];
PropertyItems = new ITaskItem[0];
Copy link
Member

@jaredpar jaredpar May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
MetadataItems = new ITaskItem[0];
PropertyItems = new ITaskItem[0];
MetadataItems = Array.Empty<ITaskItem>();
PropertyItems = Array.Empty<ITaskItem>();
``` #Resolved

}

// group the metadata items by their full path
var groupedItems = MetadataItems.GroupBy(i => i.GetMetadata("FullPath"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for GetMetadata("FullPath") to return null?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that it's 'well known metadata item' (https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-well-known-item-metadata?view=vs-2019) so will always have a value. @rainersigwald to verify that though.

There is a test ItemIsNotFullyQualifiedPath that codifies what happens when there isn't a real item on disk.


In reply to: 419752014 [](ancestors = 419752014)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, follows the "you asked for meaningless garbage? I'll give you string.Empty" philosophy of MSBuild.

(It's not a good philosophy. But it's the one we have.)

foreach (var prop in PropertyItems)
{
builder.Append("msbuild_property.")
.Append(prop.ItemSpec)
Copy link
Member

@jaredpar jaredpar May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does ItemSpec return in this context? #Resolved

Copy link
Member Author

@chsienki chsienki May 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of the actual property. ItemSpec is what's in the Include attribute. We include the properties on line 103 of Microsoft.Managed.Core.targets split out by their values.


In reply to: 419752343 [](ancestors = 419752343)

@chsienki
Copy link
Member Author

chsienki commented May 4, 2020

Done. The actual parsing of these is in the other PR #42219. There we treat the sections differently if it's a global config; really we're building a new thing (a global analyzer config) that just happens to look a bit like an editor config. But I agree we should make it as interoperable as possible.


In reply to: 623657653 [](ancestors = 623657653)

///
/// Each of the <see cref="MetadataItems"/> will be transformed into a new section in the generated config file. The section
/// header will be the full path of the item (generated via its<see cref="ITaskItem.ItemSpec"/>), and each section will have a
/// set of <c>msbuild_metadata.<em>ItemType</em>.<em>MetadataName</em> = <em>RetreivedMetadataValue</em></c>, one per <c>ItemType</c>
Copy link
Member

@jcouv jcouv May 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retreived [](start = 81, length = 9)

nit: typo "Retrieved" #Resolved

/// in the given string with '/'. Otherwise, returns the string.
/// </summary>
/// <remarks>
/// This method is equivalent to Microsoft.CodeAnalysis.BuildTasks.GenerateMSBuildAnalyzerConfig.NormalizeWithForwardSlash
Copy link
Member

@jcouv jcouv May 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Microsoft.CodeAnalysis.BuildTasks.GenerateMSBuildAnalyzerConfig.NormalizeWithForwardSlash [](start = 41, length = 89)

nit: consider making it a link (<see I think). Same on other end #WontFix

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't sadly, because they don't reference each other (why we have to copy). It issues a warning that it can't be resolved.


In reply to: 419837629 [](ancestors = 419837629)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks (iteration 3) with couple nits

@jcouv jcouv self-assigned this May 5, 2020
builder.Append("msbuild_item.")
.Append(item.GetMetadata("ItemType"))
.Append(".")
.Append(metadataName)
Copy link
Member

@jaredpar jaredpar May 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will metadataName always be a valid identifier for a editor config value name here? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Editorconfig keys are opaque strings; the only disallowed characters are whitespace and '=' (see https://editorconfig-specification.readthedocs.io/en/latest/#id3).

metadataName is the value of either an xml element or attribute name (note: the name, not it's value). The XML spec disallows '=' or ' ' as identifier characters (https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Name) which means it will always be valid.


In reply to: 419844440 [](ancestors = 419844440)

Assert.Equal(@"is_global = true

[c:/file1.cs]
msbuild_item.. =
Copy link
Member

@jaredpar jaredpar May 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this legal editor config? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It's just a string. It's semantically uninteresting to us, but valid.


In reply to: 419847799 [](ancestors = 419847799)

/// The Microsoft.Managed.Core.targets calls this task with the collected results of the <c>AnalyzerProperty</c> and
/// <c>AnalyzerItemMetadata</c> item groups.
/// </remarks>
public sealed class GenerateMSBuildAnalyzerConfig : Task
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One part I'm unclear on is what is the responsibility of this type with respect to generating a well formed / correct editorconfig file? Is the expectation that this Task is best effort, guaranteed to produce correct editor config, etc ...?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be a valid config, although there is the newline bug that means the files it produces can be incorrect. Correct parsing of editorconfig is supposed to just ignore lines it can't understand though, so even then, it'll be valid you just can't read the whole property.

Want me to add something to the task description that explains it?


In reply to: 419847995 [](ancestors = 419847995)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly add a remarks section essentially noting that there will always be cases where we can produce invalid editorconfig files. We can definitely get the 99% case covered but I'm sure there are ways that developers could do evil things to produce an invalid ones.

Another question, @jasonmalinowski brought this up in chat. When the editor config is invalid how will the customer find out? Will there just be an error message in the diagonstics for example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An editorconfig parsed is supposed to ignore any lines it doesn't understand when parsing: I've checked and we can handle a malformed config; we just ignore anything we can't parse. Of course that isn't particularly helpful for the user to understand when things aren't working. We 'blessed' the diagnostic options as having warnings when we don't understand them, and I expect we'll do the same for the msbuild properties generated here. The task should always generate something we can recognize (msbuild_property / msbuild_metadata) so when it comes to parsing those (another PR) will probably issue warnings for things we can't understand.


In reply to: 420302735 [](ancestors = 420302735,419847995)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An editorconfig parsed is supposed to ignore any lines it doesn't understand when parsing: I've checked and we can handle a malformed config; we just ignore anything we can't parse. Of course that isn't particularly helpful for the user to understand when things aren't working. We 'blessed' the diagnostic options as having warnings when we don't understand them, and I expect we'll do the same for the msbuild properties generated here. The task should always generate something we can recognize (msbuild_property / msbuild_metadata) so when it comes to parsing those (another PR) will probably issue warnings for things we can't understand.


In reply to: 420351311 [](ancestors = 420351311)

Comment on lines +82 to +83
<GeneratedMSBuildAnalyzerConfigFile Condition="'$(GeneratedMSBuildAnalyzerConfigFile)' ==''">$(IntermediateOutputPath)$(MSBuildProjectName).GeneratedMSBuildAnalyzerConfig.editorconfig</GeneratedMSBuildAnalyzerConfigFile>
<GenerateMSBuildAnalyzerConfigFile Condition="'$(GenerateMSBuildAnalyzerConfigFile)' == ''">true</GenerateMSBuildAnalyzerConfigFile>
Copy link
Member

@jaredpar jaredpar May 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<GeneratedMSBuildAnalyzerConfigFile Condition="'$(GeneratedMSBuildAnalyzerConfigFile)' ==''">$(IntermediateOutputPath)$(MSBuildProjectName).GeneratedMSBuildAnalyzerConfig.editorconfig</GeneratedMSBuildAnalyzerConfigFile>
<GenerateMSBuildAnalyzerConfigFile Condition="'$(GenerateMSBuildAnalyzerConfigFile)' == ''">true</GenerateMSBuildAnalyzerConfigFile>
<GeneratedMSBuildAnalyzerConfigFile Condition="'$(GeneratedMSBuildAnalyzerConfigFile)' == ''">$(IntermediateOutputPath)$(MSBuildProjectName).GeneratedMSBuildAnalyzerConfig.editorconfig</GeneratedMSBuildAnalyzerConfigFile>
<GenerateMSBuildAnalyzerConfigFile Condition="'$(GenerateMSBuildAnalyzerConfigFile)' ==
''">true</GenerateMSBuildAnalyzerConfigFile>

Sorry, that was driving me crazy. #Resolved

[{Path.GetFullPath("..\\file2.cs").Replace('\\', '/')}]
msbuild_item.Compile.ToRetrieve = abc123

[{Path.GetFullPath("someDir\\otherDir\\thirdDir\\..\\file3.cs").Replace('\\', '/')}]
Copy link
Member

@jaredpar jaredpar May 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this test functioning? I can't see where these files are written to disk hence can't see how this not throwing. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not writing anything to disk. MSBuild takes the value of the ItemSpec and appends the Working Directory to it when you ask for the full path. The tests here are just doing that same calculation so it works without having to hard code absolute paths.

I'll update the test to make it a little clearer what's going on.


In reply to: 419849389 [](ancestors = 419849389)

/// The Microsoft.Managed.Core.targets calls this task with the collected results of the <c>AnalyzerProperty</c> and
/// <c>AnalyzerItemMetadata</c> item groups.
/// </remarks>
public sealed class GenerateMSBuildAnalyzerConfig : Task
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly add a remarks section essentially noting that there will always be cases where we can produce invalid editorconfig files. We can definitely get the 99% case covered but I'm sure there are ways that developers could do evil things to produce an invalid ones.

Another question, @jasonmalinowski brought this up in chat. When the editor config is invalid how will the customer find out? Will there just be an error message in the diagonstics for example?

@chsienki chsienki merged commit 0853fde into dotnet:master May 6, 2020
@ghost ghost added this to the Next milestone May 6, 2020
@chsienki chsienki deleted the msbuild_to_editorconfig branch May 6, 2020 01:33
333fred added a commit to 333fred/roslyn that referenced this pull request May 6, 2020
* upstream/master: (495 commits)
  Simplify PEPropertySymbol constructor (dotnet#43990)
  Remove duplicate calls to ThrowIfCancellationRequested
  Allow msbuild to pass properties and metadata as analyzerconfig: (dotnet#43617)
  Use a private exception type for error types encountered instead of ArgumentException.
  Remove unnecessary check
  Code review feedback
  Adding tests and expanding side effect cases
  Expose specific members of System.[U]IntPtr from native integer types (dotnet#43766)
  Remove trailing space from BoundDecisionDagNode
  Don't cache FileInfo instances.
  Add 16.7 Preview 1 to the publish data config
  Bump prerelease version for 16.7 preview 2
  Only run code style analyzers when nullable warnings are enabled
  Remove
  Code review feedback
  Lint
  Undo
  Fix regression in global:: qualified constant in a switch case. Fixes dotnet#43960
  Undo
  Simplify
  ...
@JoeRobich JoeRobich modified the milestones: Next, 16.7.P2 May 18, 2020
@chsienki chsienki restored the msbuild_to_editorconfig branch April 1, 2021 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants