Skip to content

Commit

Permalink
Add validation that executable references match SelfContained
Browse files Browse the repository at this point in the history
  • Loading branch information
dsplaisted committed Dec 31, 2020
1 parent 4ec3a71 commit 166aa0f
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Framework;

namespace Microsoft.NET.Build.Tasks
{
public class ValidateExecutableReferences : TaskBase
{
public bool SelfContained { get; set; }

public bool IsExecutable { get; set; }

public ITaskItem[] ReferencedProjects { get; set; } = Array.Empty<ITaskItem>();

protected override void ExecuteCore()
{
if (!IsExecutable)
{
// If current project is not executable, then we don't need to check its references
return;
}

foreach (var project in ReferencedProjects)
{
string nearestTargetFramework = project.GetMetadata("NearestTargetFramework");
int targetFrameworkIndex = project.GetMetadata("TargetFrameworks").Split(';').ToList().IndexOf(nearestTargetFramework);
string projectAdditionalPropertiesMetadata = project.GetMetadata("AdditionalPropertiesFromProject").Split(new[] { ";;" }, StringSplitOptions.None)[targetFrameworkIndex];
Dictionary<string, string> projectAdditionalProperties = new(StringComparer.OrdinalIgnoreCase);
foreach (var propAndValue in projectAdditionalPropertiesMetadata.Split(';'))
{
var split = propAndValue.Split('=');
projectAdditionalProperties[split[0]] = split[1];
}

var referencedProjectIsExecutable = MSBuildUtilities.ConvertStringToBool(projectAdditionalProperties["_IsExecutable"]);
var referencedProjectIsSelfContained = MSBuildUtilities.ConvertStringToBool(projectAdditionalProperties["SelfContained"]);

if (referencedProjectIsExecutable)
{
if (SelfContained && !referencedProjectIsSelfContained)
{
Log.LogError(Strings.SelfContainedExeCannotReferenceNonSelfContained, project.ItemSpec);
}
else if (!SelfContained && referencedProjectIsSelfContained)
{
Log.LogError(Strings.NonSelfContainedExeCannottReferenceSelfContained, project.ItemSpec);
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,31 @@ Copyright (c) .NET Foundation. All rights reserved.
</GenerateSupportedTargetFrameworkAlias>
</Target>

<!--
============================================================
ValidateExecutableReferences
============================================================
-->

<ItemGroup>
<AdditionalTargetFrameworkInfoProperty Include="SelfContained"/>
<AdditionalTargetFrameworkInfoProperty Include="_IsExecutable"/>
</ItemGroup>

<UsingTask TaskName="ValidateExecutableReferences" AssemblyFile="$(MicrosoftNETBuildTasksAssembly)" />

<Target Name="ValidateExecutableReferences"
AfterTargets="_GetProjectReferenceTargetFrameworkProperties"
Condition="'$(ValidateExecutableReferencesMatchSelfContained)' != 'false'">

<ValidateExecutableReferences
SelfContained="$(SelfContained)"
IsExecutable="$(_IsExecutable)"
ReferencedProjects="@(_MSBuildProjectReferenceExistent)"
/>

</Target>

<!--
============================================================
Project Capabilities
Expand Down
112 changes: 79 additions & 33 deletions src/Tests/Microsoft.NET.Build.Tests/ReferenceExeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,50 +76,52 @@ private void CreateProjects()
MainProject.ReferencedProjects.Add(ReferencedProject);
}

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

string outputDirectory;

TestCommand buildOrPublishCommand;

if (TestWithPublish)
{
var publishCommand = new PublishCommand(testProjectInstance);

publishCommand.Execute()
.Should()
.Pass();

outputDirectory = publishCommand.GetOutputDirectory(MainProject.TargetFrameworks, runtimeIdentifier: MainProject.RuntimeIdentifier).FullName;

buildOrPublishCommand = publishCommand;
}
else
{
var buildCommand = new BuildCommand(testProjectInstance);

buildCommand.Execute()
.Should()
.Pass();

outputDirectory = buildCommand.GetOutputDirectory(MainProject.TargetFrameworks, runtimeIdentifier: MainProject.RuntimeIdentifier).FullName;

buildOrPublishCommand = buildCommand;
}

var mainExePath = Path.Combine(outputDirectory, MainProject.Name + Constants.ExeSuffix);
if (buildFailureCode == null)
{
buildOrPublishCommand.Execute()
.Should()
.Pass();

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

new RunExeCommand(Log, mainExePath)
.Execute()
.Should()
.Pass()
.And
.HaveStdOut("Main project");
var referencedExePath = Path.Combine(outputDirectory, ReferencedProject.Name + Constants.ExeSuffix);

new RunExeCommand(Log, mainExePath)
.Execute()
.Should()
.Pass()
.And
.HaveStdOut("Main project");

var referencedExeResult = new RunExeCommand(Log, referencedExePath)
.Execute();

if (referencedExeShouldRun)
{
var referencedExeResult = new RunExeCommand(Log, referencedExePath)
.Execute();

referencedExeResult
.Should()
.Pass()
Expand All @@ -128,13 +130,15 @@ private void RunTest(bool referencedExeShouldRun, [CallerMemberName] string call
}
else
{
referencedExeResult
// Build should not succeed
buildOrPublishCommand.Execute()
.Should()
.Fail();
}
.Fail()
.And
.HaveStdOutContaining(buildFailureCode);
}
}


[Theory]
[InlineData(false, false)]
[InlineData(true, true)]
Expand All @@ -145,7 +149,7 @@ public void ReferencedExeCanRun(bool mainSelfContained, bool referencedSelfConta

CreateProjects();

RunTest(true);
RunTest();
}

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

RunTest(true);
RunTest();
}

// Having a self-contained and a framework-dependent app in the same folder is not supported (due to the way the host works).
// The referenced app will fail to run. See here for more details: https://github.com/dotnet/sdk/pull/14488#issuecomment-725406998
[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
public void ReferencedExeFailsToRun(bool mainSelfContained, bool referencedSelfContained)
[InlineData(true, false, "NETSDK1148")]
[InlineData(false, true, "NETSDK1149")]
public void ReferencedExeFailsToBuild(bool mainSelfContained, bool referencedSelfContained, string expectedFailureCode)
{
MainSelfContained = mainSelfContained;
ReferencedSelfContained = referencedSelfContained;

CreateProjects();

RunTest(referencedExeShouldRun: false);
RunTest(expectedFailureCode);
}

[Fact]
public void ReferencedExeCanRunWhenReferencesExeWithSelfContainedMismatchForDifferentTargetFramework()
{
MainSelfContained = true;
ReferencedSelfContained = false;

CreateProjects();

// Reference project which is self-contained for net5.0, not self-contained for net5.0-windows.
ReferencedProject.TargetFrameworks = "net5.0;net5.0-windows";
ReferencedProject.ProjectChanges.Add(project =>
{
var ns = project.Root.Name.Namespace;

project.Root.Element(ns + "PropertyGroup")
.Add(XElement.Parse(@"<RuntimeIdentifier Condition=""'$(TargetFramework)' == 'net5.0'"">" + EnvironmentInfo.GetCompatibleRid() + "</RuntimeIdentifier>"));
});

RunTest();
}

[Fact]
public void ReferencedExeFailsToBuildWhenReferencesExeWithSelfContainedMismatchForSameTargetFramework()
{
MainSelfContained = true;
ReferencedSelfContained = false;

CreateProjects();

// Reference project which is self-contained for net5.0-windows, not self-contained for net5.0.
ReferencedProject.TargetFrameworks = "net5.0;net5.0-windows";
ReferencedProject.ProjectChanges.Add(project =>
{
var ns = project.Root.Name.Namespace;

project.Root.Element(ns + "PropertyGroup")
.Add(XElement.Parse(@"<RuntimeIdentifier Condition=""'$(TargetFramework)' == 'net5.0-windows'"">" + EnvironmentInfo.GetCompatibleRid() + "</RuntimeIdentifier>"));
});

RunTest("NETSDK1148");
}

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

CreateProjects();

RunTest(referencedExeShouldRun: true);
RunTest();
}

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

RunTest(referencedExeShouldRun: true);
RunTest();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public TestProject([CallerMemberName] string name = null)

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

public List<Action<XDocument>> ProjectChanges { get; } = new List<Action<XDocument>>();

public IEnumerable<string> TargetFrameworkIdentifiers
{
get
Expand Down Expand Up @@ -311,6 +313,11 @@ internal void Create(TestAsset targetTestAsset, string testProjectsSourceFolder,
}
}

foreach (var projectChange in ProjectChanges)
{
projectChange(projectXml);
}

using (var file = File.CreateText(targetProjectPath))
{
projectXml.Save(file);
Expand Down

0 comments on commit 166aa0f

Please sign in to comment.