Skip to content
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

Add TargetFramework(s) unexpected check #11285

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion documentation/specs/BuildCheck/Codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Report codes are chosen to conform to suggested guidelines. Those guidelines are
| [BC0104](#bc0104---projectreference-is-preferred-to-reference) | Warning | N/A | 9.0.200 | ProjectReference is preferred to Reference. |
| [BC0105](#bc0105---embeddedresource-should-specify-culture-metadata) | Warning | N/A | 9.0.200 | Culture specific EmbeddedResource should specify Culture metadata. |
| [BC0106](#bc0106---copytooutputdirectoryalways-should-be-avoided) | Warning | N/A | 9.0.200 | CopyToOutputDirectory='Always' should be avoided. |
| [BC0107](#bc0107---targetframework-and-targetframeworks-specified-together) | Warning | N/A | 9.0.200 | TargetFramework and TargetFrameworks specified together. |
| [BC0107](#bc0107---targetframework-and-targetframeworks-specified-together) | Warning | N/A | 9.0.200 | TargetFramework or TargetFrameworks specified in non-SDK style project. |
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved
| [BC0108](#bc0108---targetframework-or-targetframeworks-specified-in-non-sdk-style-project) | Warning | N/A | 9.0.300 | TargetFramework and TargetFrameworks specified together. |
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved
| [BC0201](#bc0201---usage-of-undefined-property) | Warning | Project | 9.0.100 | Usage of undefined property. |
| [BC0202](#bc0202---property-first-declared-after-it-was-used) | Warning | Project | 9.0.100 | Property first declared after it was used. |
| [BC0203](#bc0203----property-declared-but-never-used) | None | Project | 9.0.100 | Property declared but never used. |
Expand Down Expand Up @@ -126,6 +127,16 @@ If you specify `TargetFramework` you are instructing the build to produce a sing
dotnet build my-multi-target.csproj /p:TargetFramework=net9.0
```

<a name="BC0108"></a>
## BC0108 - TargetFramework or TargetFrameworks specified in non-SDK style project.

"'TargetFramework' nor 'TargetFrameworks' property should not be specified in non-SDK style projects."

'TargetFramework' or 'TargetFrameworks' control the project output targets in modern .NET SDK-style projects. The older non-SDK style projects ('legacy style' projects) interprets different properties for similar mechanism (like 'TargetFrameworkVersion') and the 'TargetFramework' or 'TargetFrameworks' are silently ignored.
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved

Make sure the Target Framework targetting is done by properly understood mechanism.
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved


<a name="BC0201"></a>
## BC0201 - Usage of undefined property.

Expand Down
66 changes: 66 additions & 0 deletions src/Build/BuildCheck/Checks/TargetFrameworkUnexpectedCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Collections;
using Microsoft.Build.Construction;
using Microsoft.Build.Shared;

namespace Microsoft.Build.Experimental.BuildCheck.Checks;
internal class TargetFrameworkUnexpectedCheck : Check
{
private const string RuleId = "BC0108";
public static CheckRule SupportedRule = new CheckRule(RuleId, "TargetFrameworkUnexpected",
ResourceUtilities.GetResourceString("BuildCheck_BC0108_Title")!,
ResourceUtilities.GetResourceString("BuildCheck_BC0108_MessageFmt")!,
new CheckConfiguration() { RuleId = RuleId, Severity = CheckResultSeverity.Warning });

public override string FriendlyName => "MSBuild.TargetFrameworkUnexpected";

public override IReadOnlyList<CheckRule> SupportedRules { get; } = [SupportedRule];

public override void Initialize(ConfigurationContext configurationContext)
{
/* This is it - no custom configuration */
}

public override void RegisterActions(IBuildCheckRegistrationContext registrationContext)
{
registrationContext.RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction);
}

internal override bool IsBuiltIn => true;

private readonly HashSet<string> _projectsSeen = new(MSBuildNameIgnoreCaseComparer.Default);

private void EvaluatedPropertiesAction(BuildCheckDataContext<EvaluatedPropertiesCheckData> context)
{
// We want to avoid repeated checking of a same project (as it might be evaluated multiple times)
// for this reason we use a hashset with already seen projects.
if (!_projectsSeen.Add(context.Data.ProjectFilePath))
Copy link
Member

Choose a reason for hiding this comment

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

is having another dictionary with all the project paths in it worth it memory-wise to avoid looping over ProjectCapability? Which has worse perf I wonder?

This is fine, it makes sense to do this kind of check, though I wonder if we should somehow make it cheaper for individual checks to do this sort of thing.

{
return;
}

string? frameworks = null;
string? framework = null;
// This is not SDK style project
if ((!context.Data.EvaluatedProperties.TryGetValue(PropertyNames.UsingMicrosoftNETSdk, out string? usingSdkStr) ||
Copy link
Member

Choose a reason for hiding this comment

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

If I'm reading your conditions correctly here, I think this would fire on a C++/CLI vcxproj targeting .NET 8--but shouldn't.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can detect import of Microsoft.Cpp.props to handle this.
But together with this comment: #11285 (comment) - this leads to more importnat question: do you feel this is fundamentaly wrong and would you suggest different way of detecting non-SDK project?

Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should restrict it to .csproj? Or check for any import called CSharp.targets? though I guess it should apply to vbproj and fsproj too . . .

Copy link
Member Author

Choose a reason for hiding this comment

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

As brainstormed offline - I updated the check to detect ProjectCapability and if the project is sdk-style or c++/cli

!StringExtensions.IsMSBuildTrueString(usingSdkStr))
&&
// But TargetFramework(s) is specified
(context.Data.EvaluatedProperties.TryGetValue(PropertyNames.TargetFrameworks, out frameworks) ||
context.Data.EvaluatedProperties.TryGetValue(PropertyNames.TargetFramework, out framework)) &&
!string.IsNullOrEmpty(framework ?? frameworks))
{
// {0} specifies 'TargetFrameworks' property '{1}' and 'TargetFramework' property '{2}'
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved
context.ReportResult(BuildCheckResult.Create(
SupportedRule,
// Populating precise location tracked via https://github.com/orgs/dotnet/projects/373/views/1?pane=issue&itemId=58661732
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved
ElementLocation.EmptyLocation,
Path.GetFileName(context.Data.ProjectFilePath),
framework ?? frameworks ?? string.Empty));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ internal readonly record struct BuiltInCheckFactory(
new BuiltInCheckFactory([NoEnvironmentVariablePropertyCheck.SupportedRule.Id], NoEnvironmentVariablePropertyCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct<NoEnvironmentVariablePropertyCheck>),
new BuiltInCheckFactory([EmbeddedResourceCheck.SupportedRule.Id], EmbeddedResourceCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct<EmbeddedResourceCheck>),
new BuiltInCheckFactory([TargetFrameworkConfusionCheck.SupportedRule.Id], TargetFrameworkConfusionCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct<TargetFrameworkConfusionCheck>),
new BuiltInCheckFactory([TargetFrameworkUnexpectedCheck.SupportedRule.Id], TargetFrameworkUnexpectedCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct<TargetFrameworkUnexpectedCheck>),
],

// BuildCheckDataSource.Execution
Expand Down
2 changes: 1 addition & 1 deletion src/Build/Graph/ProjectInterpretation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ public bool RequiresTransitiveProjectReferences(ProjectGraphNode projectGraphNod
// special case for Quickbuild which updates msbuild binaries independent of props/targets. Remove this when all QB repos will have
// migrated to new enough Visual Studio versions whose Microsoft.Managed.After.Targets enable transitive references.
if (string.IsNullOrWhiteSpace(projectInstance.GetEngineRequiredPropertyValue(AddTransitiveProjectReferencesInStaticGraphPropertyName)) &&
MSBuildStringIsTrue(projectInstance.GetEngineRequiredPropertyValue("UsingMicrosoftNETSdk")) &&
MSBuildStringIsTrue(projectInstance.GetEngineRequiredPropertyValue(PropertyNames.UsingMicrosoftNETSdk)) &&
MSBuildStringIsFalse(projectInstance.GetEngineRequiredPropertyValue("DisableTransitiveProjectReferences")))
{
return true;
Expand Down
8 changes: 8 additions & 0 deletions src/Build/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2198,6 +2198,14 @@ Utilization: {0} Average Utilization: {1:###.0}</value>
<value>Project {0} specifies 'TargetFrameworks' property '{1}' and 'TargetFramework' property '{2}' at the same time. This will lead to 'TargetFrameworks' being ignored and build will behave as single-targeted.</value>
<comment>Terms in quotes are not to be translated.</comment>
</data>
<data name="BuildCheck_BC0108_Title" xml:space="preserve">
<value>'TargetFramework' nor 'TargetFrameworks' property should not be specified in non-SDK style projects.</value>
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved
<comment>Terms in quotes are not to be translated.</comment>
</data>
<data name="BuildCheck_BC0108_MessageFmt" xml:space="preserve">
<value>Project {0} specifies 'TargetFramework(s)' property '{1}', while it's not SDK-style project. Those properties are not understood by legacy-style projects and have no impact.</value>
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved
<comment>Terms in quotes are not to be translated.</comment>
</data>
<data name="BuildCheck_BC0201_Title" xml:space="preserve">
<value>A property that is accessed should be declared first.</value>
</data>
Expand Down
10 changes: 10 additions & 0 deletions src/Build/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Build/Resources/xlf/Strings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Build/Resources/xlf/Strings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Build/Resources/xlf/Strings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Build/Resources/xlf/Strings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Build/Resources/xlf/Strings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Build/Resources/xlf/Strings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading