Skip to content

Commit 48a8949

Browse files
committed
Add validation that executable references match SelfContained
Fixes #15117
1 parent 9167941 commit 48a8949

File tree

4 files changed

+170
-33
lines changed

4 files changed

+170
-33
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
//
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
using Microsoft.Build.Framework;
11+
12+
namespace Microsoft.NET.Build.Tasks
13+
{
14+
public class ValidateExecutableReferences : TaskBase
15+
{
16+
public bool SelfContained { get; set; }
17+
18+
public bool IsExecutable { get; set; }
19+
20+
public ITaskItem[] ReferencedProjects { get; set; } = Array.Empty<ITaskItem>();
21+
22+
protected override void ExecuteCore()
23+
{
24+
if (!IsExecutable)
25+
{
26+
// If current project is not executable, then we don't need to check its references
27+
return;
28+
}
29+
30+
foreach (var project in ReferencedProjects)
31+
{
32+
string nearestTargetFramework = project.GetMetadata("NearestTargetFramework");
33+
int targetFrameworkIndex = project.GetMetadata("TargetFrameworks").Split(';').ToList().IndexOf(nearestTargetFramework);
34+
string projectAdditionalPropertiesMetadata = project.GetMetadata("AdditionalPropertiesFromProject").Split(new[] { ";;" }, StringSplitOptions.None)[targetFrameworkIndex];
35+
Dictionary<string, string> projectAdditionalProperties = new(StringComparer.OrdinalIgnoreCase);
36+
foreach (var propAndValue in projectAdditionalPropertiesMetadata.Split(';'))
37+
{
38+
var split = propAndValue.Split('=');
39+
projectAdditionalProperties[split[0]] = split[1];
40+
}
41+
42+
var referencedProjectIsExecutable = MSBuildUtilities.ConvertStringToBool(projectAdditionalProperties["_IsExecutable"]);
43+
var referencedProjectIsSelfContained = MSBuildUtilities.ConvertStringToBool(projectAdditionalProperties["SelfContained"]);
44+
45+
if (referencedProjectIsExecutable)
46+
{
47+
if (SelfContained && !referencedProjectIsSelfContained)
48+
{
49+
Log.LogError(Strings.SelfContainedExeCannotReferenceNonSelfContained, project.ItemSpec);
50+
}
51+
else if (!SelfContained && referencedProjectIsSelfContained)
52+
{
53+
Log.LogError(Strings.NonSelfContainedExeCannotReferenceSelfContained, project.ItemSpec);
54+
}
55+
}
56+
}
57+
}
58+
}
59+
}

src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.targets

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,31 @@ Copyright (c) .NET Foundation. All rights reserved.
10291029
</GenerateSupportedTargetFrameworkAlias>
10301030
</Target>
10311031

1032+
<!--
1033+
============================================================
1034+
ValidateExecutableReferences
1035+
============================================================
1036+
-->
1037+
1038+
<ItemGroup>
1039+
<AdditionalTargetFrameworkInfoProperty Include="SelfContained"/>
1040+
<AdditionalTargetFrameworkInfoProperty Include="_IsExecutable"/>
1041+
</ItemGroup>
1042+
1043+
<UsingTask TaskName="ValidateExecutableReferences" AssemblyFile="$(MicrosoftNETBuildTasksAssembly)" />
1044+
1045+
<Target Name="ValidateExecutableReferences"
1046+
AfterTargets="_GetProjectReferenceTargetFrameworkProperties"
1047+
Condition="'$(ValidateExecutableReferencesMatchSelfContained)' != 'false'">
1048+
1049+
<ValidateExecutableReferences
1050+
SelfContained="$(SelfContained)"
1051+
IsExecutable="$(_IsExecutable)"
1052+
ReferencedProjects="@(_MSBuildProjectReferenceExistent)"
1053+
/>
1054+
1055+
</Target>
1056+
10321057
<!--
10331058
============================================================
10341059
Project Capabilities

src/Tests/Microsoft.NET.Build.Tests/ReferenceExeTests.cs

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -76,50 +76,52 @@ private void CreateProjects()
7676
MainProject.ReferencedProjects.Add(ReferencedProject);
7777
}
7878

79-
private void RunTest(bool referencedExeShouldRun, [CallerMemberName] string callingMethod = null)
79+
private void RunTest(string buildFailureCode = null, [CallerMemberName] string callingMethod = null)
8080
{
8181
var testProjectInstance = _testAssetsManager.CreateTestProject(MainProject, callingMethod: callingMethod, identifier: MainSelfContained.ToString() + "_" + ReferencedSelfContained.ToString());
8282

8383
string outputDirectory;
8484

85+
TestCommand buildOrPublishCommand;
86+
8587
if (TestWithPublish)
8688
{
8789
var publishCommand = new PublishCommand(testProjectInstance);
8890

89-
publishCommand.Execute()
90-
.Should()
91-
.Pass();
92-
9391
outputDirectory = publishCommand.GetOutputDirectory(MainProject.TargetFrameworks, runtimeIdentifier: MainProject.RuntimeIdentifier).FullName;
92+
93+
buildOrPublishCommand = publishCommand;
9494
}
9595
else
9696
{
9797
var buildCommand = new BuildCommand(testProjectInstance);
9898

99-
buildCommand.Execute()
100-
.Should()
101-
.Pass();
102-
10399
outputDirectory = buildCommand.GetOutputDirectory(MainProject.TargetFrameworks, runtimeIdentifier: MainProject.RuntimeIdentifier).FullName;
100+
101+
buildOrPublishCommand = buildCommand;
104102
}
105103

106-
var mainExePath = Path.Combine(outputDirectory, MainProject.Name + Constants.ExeSuffix);
104+
if (buildFailureCode == null)
105+
{
106+
buildOrPublishCommand.Execute()
107+
.Should()
108+
.Pass();
107109

108-
var referencedExePath = Path.Combine(outputDirectory, ReferencedProject.Name + Constants.ExeSuffix);
110+
var mainExePath = Path.Combine(outputDirectory, MainProject.Name + Constants.ExeSuffix);
109111

110-
new RunExeCommand(Log, mainExePath)
111-
.Execute()
112-
.Should()
113-
.Pass()
114-
.And
115-
.HaveStdOut("Main project");
112+
var referencedExePath = Path.Combine(outputDirectory, ReferencedProject.Name + Constants.ExeSuffix);
116113

114+
new RunExeCommand(Log, mainExePath)
115+
.Execute()
116+
.Should()
117+
.Pass()
118+
.And
119+
.HaveStdOut("Main project");
117120

118-
var referencedExeResult = new RunExeCommand(Log, referencedExePath)
119-
.Execute();
120121

121-
if (referencedExeShouldRun)
122-
{
122+
var referencedExeResult = new RunExeCommand(Log, referencedExePath)
123+
.Execute();
124+
123125
referencedExeResult
124126
.Should()
125127
.Pass()
@@ -128,13 +130,15 @@ private void RunTest(bool referencedExeShouldRun, [CallerMemberName] string call
128130
}
129131
else
130132
{
131-
referencedExeResult
133+
// Build should not succeed
134+
buildOrPublishCommand.Execute()
132135
.Should()
133-
.Fail();
134-
}
136+
.Fail()
137+
.And
138+
.HaveStdOutContaining(buildFailureCode);
139+
}
135140
}
136141

137-
138142
[Theory]
139143
[InlineData(false, false)]
140144
[InlineData(true, true)]
@@ -145,7 +149,7 @@ public void ReferencedExeCanRun(bool mainSelfContained, bool referencedSelfConta
145149

146150
CreateProjects();
147151

148-
RunTest(true);
152+
RunTest();
149153
}
150154

151155
[Fact]
@@ -159,22 +163,64 @@ public void ReferencedExeWithLowerTargetFrameworkCanRun()
159163
ReferencedProject.TargetFrameworks = "netcoreapp3.1";
160164
ReferencedProject.AdditionalProperties["LangVersion"] = "9.0";
161165

162-
RunTest(true);
166+
RunTest();
163167
}
164168

165169
// Having a self-contained and a framework-dependent app in the same folder is not supported (due to the way the host works).
166170
// The referenced app will fail to run. See here for more details: https://github.com/dotnet/sdk/pull/14488#issuecomment-725406998
167171
[Theory]
168-
[InlineData(true, false)]
169-
[InlineData(false, true)]
170-
public void ReferencedExeFailsToRun(bool mainSelfContained, bool referencedSelfContained)
172+
[InlineData(true, false, "NETSDK1150")]
173+
[InlineData(false, true, "NETSDK1151")]
174+
public void ReferencedExeFailsToBuild(bool mainSelfContained, bool referencedSelfContained, string expectedFailureCode)
171175
{
172176
MainSelfContained = mainSelfContained;
173177
ReferencedSelfContained = referencedSelfContained;
174178

175179
CreateProjects();
176180

177-
RunTest(referencedExeShouldRun: false);
181+
RunTest(expectedFailureCode);
182+
}
183+
184+
[Fact]
185+
public void ReferencedExeCanRunWhenReferencesExeWithSelfContainedMismatchForDifferentTargetFramework()
186+
{
187+
MainSelfContained = true;
188+
ReferencedSelfContained = false;
189+
190+
CreateProjects();
191+
192+
// Reference project which is self-contained for net5.0, not self-contained for net5.0-windows.
193+
ReferencedProject.TargetFrameworks = "net5.0;net5.0-windows";
194+
ReferencedProject.ProjectChanges.Add(project =>
195+
{
196+
var ns = project.Root.Name.Namespace;
197+
198+
project.Root.Element(ns + "PropertyGroup")
199+
.Add(XElement.Parse(@"<RuntimeIdentifier Condition=""'$(TargetFramework)' == 'net5.0'"">" + EnvironmentInfo.GetCompatibleRid() + "</RuntimeIdentifier>"));
200+
});
201+
202+
RunTest();
203+
}
204+
205+
[Fact]
206+
public void ReferencedExeFailsToBuildWhenReferencesExeWithSelfContainedMismatchForSameTargetFramework()
207+
{
208+
MainSelfContained = true;
209+
ReferencedSelfContained = false;
210+
211+
CreateProjects();
212+
213+
// Reference project which is self-contained for net5.0-windows, not self-contained for net5.0.
214+
ReferencedProject.TargetFrameworks = "net5.0;net5.0-windows";
215+
ReferencedProject.ProjectChanges.Add(project =>
216+
{
217+
var ns = project.Root.Name.Namespace;
218+
219+
project.Root.Element(ns + "PropertyGroup")
220+
.Add(XElement.Parse(@"<RuntimeIdentifier Condition=""'$(TargetFramework)' == 'net5.0-windows'"">" + EnvironmentInfo.GetCompatibleRid() + "</RuntimeIdentifier>"));
221+
});
222+
223+
RunTest("NETSDK1150");
178224
}
179225

180226
[Theory]
@@ -189,7 +235,7 @@ public void ReferencedExeCanRunWhenPublished(bool selfContained)
189235

190236
CreateProjects();
191237

192-
RunTest(referencedExeShouldRun: true);
238+
RunTest();
193239
}
194240

195241
[Fact]
@@ -211,7 +257,7 @@ public void ReferencedExeCanRunWhenPublishedWithTrimming()
211257
ReferencedProject.AdditionalProperties["PublishTrimmed"] = "True";
212258
}
213259

214-
RunTest(referencedExeShouldRun: true);
260+
RunTest();
215261
}
216262
}
217263
}

src/Tests/Microsoft.NET.TestFramework/ProjectConstruction/TestProject.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public TestProject([CallerMemberName] string name = null)
6161

6262
public Dictionary<string, string> AdditionalItems { get; } = new Dictionary<string, string>();
6363

64+
public List<Action<XDocument>> ProjectChanges { get; } = new List<Action<XDocument>>();
65+
6466
public IEnumerable<string> TargetFrameworkIdentifiers
6567
{
6668
get
@@ -311,6 +313,11 @@ internal void Create(TestAsset targetTestAsset, string testProjectsSourceFolder,
311313
}
312314
}
313315

316+
foreach (var projectChange in ProjectChanges)
317+
{
318+
projectChange(projectXml);
319+
}
320+
314321
using (var file = File.CreateText(targetProjectPath))
315322
{
316323
projectXml.Save(file);

0 commit comments

Comments
 (0)