Skip to content

Commit a990f3a

Browse files
authored
Merge pull request #28717 from nagilson/nagilson-publish-rid
Introduce PublishRuntimeIdentifier
2 parents 075dde1 + 13f70ed commit a990f3a

File tree

7 files changed

+124
-14
lines changed

7 files changed

+124
-14
lines changed

src/Cli/dotnet/commands/dotnet-publish/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static PublishCommand FromParseResult(ParseResult parseResult, string msb
4646
var msbuildArgs = new List<string>()
4747
{
4848
"-target:Publish",
49-
"--property:_IsPublishing=true"
49+
"--property:_IsPublishing=true" // This property will not hold true for MSBuild /t:Publish. VS should also inject this property when publishing in the future.
5050
};
5151

5252
IEnumerable<string> slnOrProjectArgs = parseResult.GetValueForArgument(PublishCommandParser.SlnOrProjectArgument);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ Copyright (c) .NET Foundation. All rights reserved.
7878
<RuntimeIdentifier>$(NETCoreSdkPortableRuntimeIdentifier)</RuntimeIdentifier>
7979
</PropertyGroup>
8080

81+
<PropertyGroup Condition="'$(_IsPublishing)' == 'true' and '$(PublishRuntimeIdentifier)' != ''">
82+
<RuntimeIdentifier>$(PublishRuntimeIdentifier)</RuntimeIdentifier>
83+
</PropertyGroup>
84+
8185
<PropertyGroup Condition="'$(PlatformTarget)' == ''">
8286
<_UsingDefaultPlatformTarget>true</_UsingDefaultPlatformTarget>
8387
</PropertyGroup>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ private static void ValidateProperties(TestAsset testAsset, TestProject testProj
260260
expectedRuntimeIdentifier = Path.GetFileName(Directory.GetDirectories(dir).Single());
261261
}
262262

263-
var properties = testProject.GetPropertyValues(testAsset.TestRoot, targetFramework: targetFramework, runtimeIdentifier: expectedRuntimeIdentifier);
263+
var properties = testProject.GetPropertyValues(testAsset.TestRoot, targetFramework: targetFramework);
264264
if (expectSelfContained)
265265
{
266266
properties["SelfContained"].ToLowerInvariant().Should().Be("true");

src/Tests/Microsoft.NET.Publish.Tests/RuntimeIdentifiersTests.cs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Linq;
8+
using System.Reflection;
9+
using System.Security.Cryptography;
810
using System.Text;
911
using System.Xml.Linq;
1012
using FluentAssertions;
@@ -15,6 +17,7 @@
1517
using Microsoft.NET.TestFramework.ProjectConstruction;
1618
using Xunit;
1719
using Xunit.Abstractions;
20+
using Xunit.Sdk;
1821

1922
namespace Microsoft.NET.Publish.Tests
2023
{
@@ -94,8 +97,6 @@ public void BuildWithUseCurrentRuntimeIdentifier()
9497
IsExe = true
9598
};
9699

97-
var compatibleRid = EnvironmentInfo.GetCompatibleRid(testProject.TargetFrameworks);
98-
99100
testProject.AdditionalProperties["UseCurrentRuntimeIdentifier"] = "True";
100101

101102
// Use a test-specific packages folder
@@ -194,6 +195,81 @@ public void PublishWithRuntimeIdentifier(bool publishNoBuild)
194195
}
195196
}
196197

198+
[Theory]
199+
[InlineData(false, false)] // publish rid overrides rid in project file if publishing
200+
[InlineData(true, false)] // publish rid doesnt override global rid
201+
[InlineData(true, true)] // publish rid doesnt override global rid, even if global
202+
public void PublishRuntimeIdentifierSetsRuntimeIdentifierAndDoesOrDoesntOverrideRID(bool runtimeIdentifierIsGlobal, bool publishRuntimeIdentifierIsGlobal)
203+
{
204+
string tfm = ToolsetInfo.CurrentTargetFramework;
205+
string publishRuntimeIdentifier = "win-x64";
206+
string runtimeIdentifier = "win-x86";
207+
208+
var testProject = new TestProject()
209+
{
210+
IsExe = true,
211+
TargetFrameworks = tfm
212+
};
213+
if (!publishRuntimeIdentifierIsGlobal)
214+
testProject.AdditionalProperties["PublishRuntimeIdentifier"] = publishRuntimeIdentifier;
215+
if (!runtimeIdentifierIsGlobal)
216+
testProject.AdditionalProperties["RuntimeIdentifier"] = runtimeIdentifier;
217+
testProject.RecordProperties("RuntimeIdentifier");
218+
219+
List<string> args = new List<string>
220+
{
221+
runtimeIdentifierIsGlobal ? $"/p:RuntimeIdentifier={runtimeIdentifier}" : "",
222+
publishRuntimeIdentifierIsGlobal ? $"/p:PublishRuntimeIdentifier={publishRuntimeIdentifier}" : ""
223+
};
224+
225+
string identifier = $"PublishRuntimeIdentifierOverrides-{publishRuntimeIdentifierIsGlobal}-{runtimeIdentifierIsGlobal}";
226+
var testAsset = _testAssetsManager.CreateTestProject(testProject, identifier: identifier);
227+
var publishCommand = new DotnetPublishCommand(Log);
228+
publishCommand
229+
.WithWorkingDirectory(Path.Combine(testAsset.TestRoot, testProject.Name))
230+
.Execute(args.ToArray())
231+
.Should()
232+
.Pass();
233+
234+
string expectedRid = runtimeIdentifierIsGlobal ? runtimeIdentifier : publishRuntimeIdentifier;
235+
var properties = testProject.GetPropertyValues(testAsset.TestRoot, targetFramework: tfm);
236+
var finalRid = properties["RuntimeIdentifier"];
237+
238+
Assert.True(finalRid == expectedRid);
239+
}
240+
241+
[WindowsOnlyFact]
242+
public void PublishRuntimeIdentifierOverridesUseCurrentRuntime()
243+
{
244+
string tfm = ToolsetInfo.CurrentTargetFramework;
245+
string publishRid = "linux-x64"; // linux is arbitrarily picked; just because it is different than a windows RID.
246+
var testProject = new TestProject()
247+
{
248+
IsExe = true,
249+
TargetFrameworks = tfm
250+
};
251+
252+
testProject.AdditionalProperties["UseCurrentRuntimeIdentifier"] = "true";
253+
testProject.AdditionalProperties["PublishRuntimeIdentifier"] = publishRid;
254+
testProject.RecordProperties("RuntimeIdentifier");
255+
testProject.RecordProperties("NETCoreSdkPortableRuntimeIdentifier");
256+
257+
var testAsset = _testAssetsManager.CreateTestProject(testProject);
258+
var publishCommand = new DotnetPublishCommand(Log);
259+
publishCommand
260+
.WithWorkingDirectory(Path.Combine(testAsset.TestRoot, MethodBase.GetCurrentMethod().Name))
261+
.Execute()
262+
.Should()
263+
.Pass();
264+
265+
var properties = testProject.GetPropertyValues(testAsset.TestRoot, targetFramework: tfm);
266+
var finalRid = properties["RuntimeIdentifier"];
267+
var ucrRid = properties["NETCoreSdkPortableRuntimeIdentifier"];
268+
269+
Assert.True(finalRid == publishRid);
270+
Assert.True(ucrRid != finalRid);
271+
}
272+
197273
[Fact]
198274
public void ImplicitRuntimeIdentifierOptOutCorrecltyOptsOut()
199275
{

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,19 @@ public TestProject([CallerMemberName] string name = null)
2020
}
2121
}
2222

23+
/// <summary>
24+
/// A name for the test project that's used to isolate it from a test's root folder by appending it to the root test path.
25+
/// By default, it is the unhashed name of the function that instantiated the TestProject object.
26+
/// </summary>
2327
public string Name { get; set; }
2428

2529
public bool IsSdkProject { get; set; } = true;
2630

2731
public bool IsExe { get; set; }
2832

33+
/// <summary>
34+
/// This value merely sets the OutputType and is not automatically tied here to whether the project is a WPF or Windows Form App Executable.
35+
/// </summary>
2936
public bool IsWinExe { get; set; }
3037

3138
public string ProjectSdk { get; set; }
@@ -51,16 +58,19 @@ public TestProject([CallerMemberName] string name = null)
5158
public List<TestPackageReference> PackageReferences { get; } = new List<TestPackageReference>();
5259

5360
public List<TestPackageReference> DotNetCliToolReferences { get; } = new List<TestPackageReference>();
54-
61+
5562
public List<CopyFilesTarget> CopyFilesTargets { get; } = new List<CopyFilesTarget>();
5663

5764
public Dictionary<string, string> SourceFiles { get; } = new Dictionary<string, string>();
5865

5966
public Dictionary<string, string> EmbeddedResources { get; } = new Dictionary<string, string>();
6067

68+
/// <summary>
69+
/// Use this dictionary to set a property (the key) to a value for the created project.
70+
/// </summary>
6171
public Dictionary<string, string> AdditionalProperties { get; } = new Dictionary<string, string>();
6272

63-
public List<KeyValuePair<string, Dictionary<string, string>>> AdditionalItems { get; } = new ();
73+
public List<KeyValuePair<string, Dictionary<string, string>>> AdditionalItems { get; } = new();
6474

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

@@ -299,7 +309,7 @@ internal void Create(TestAsset targetTestAsset, string testProjectsSourceFolder,
299309
new XAttribute("Include", frameworkReference)));
300310
}
301311
}
302-
312+
303313
if (this.CopyFilesTargets.Any())
304314
{
305315
foreach (var copyFilesTarget in CopyFilesTargets)
@@ -420,7 +430,7 @@ public class {safeThisName}Class
420430
{propertiesElements}
421431
</ItemGroup>
422432
<WriteLinesToFile
423-
File=`$(IntermediateOutputPath)\PropertyValues.txt`
433+
File=`$(BaseIntermediateOutputPath)\$(Configuration)\$(TargetFramework)\PropertyValues.txt`
424434
Lines=`@(LinesToWrite)`
425435
Overwrite=`true`
426436
Encoding=`Unicode`
@@ -443,7 +453,7 @@ public class {safeThisName}Class
443453

444454
public void AddItem(string itemName, string attributeName, string attributeValue)
445455
{
446-
AddItem(itemName, new Dictionary<string, string>() { { attributeName, attributeValue } } );
456+
AddItem(itemName, new Dictionary<string, string>() { { attributeName, attributeValue } });
447457
}
448458

449459
public void AddItem(string itemName, Dictionary<string, string> attributes)
@@ -456,15 +466,15 @@ public void RecordProperties(params string[] propertyNames)
456466
PropertiesToRecord.AddRange(propertyNames);
457467
}
458468

459-
public Dictionary<string, string> GetPropertyValues(string testRoot, string configuration = "Debug", string targetFramework = null, string runtimeIdentifier = null)
469+
/// <returns>
470+
/// A dictionary of property keys to property value strings, case sensitive.
471+
/// Only properties added to the <see cref="PropertiesToRecord"/> member will be observed.
472+
/// </returns>
473+
public Dictionary<string, string> GetPropertyValues(string testRoot, string configuration = "Debug", string targetFramework = null)
460474
{
461475
var propertyValues = new Dictionary<string, string>();
462476

463477
string intermediateOutputPath = Path.Combine(testRoot, Name, "obj", configuration, targetFramework ?? TargetFrameworks);
464-
if (!string.IsNullOrEmpty(runtimeIdentifier))
465-
{
466-
intermediateOutputPath = Path.Combine(intermediateOutputPath, runtimeIdentifier);
467-
}
468478

469479
foreach (var line in File.ReadAllLines(Path.Combine(intermediateOutputPath, "PropertyValues.txt")))
470480
{

src/Tests/Microsoft.NET.TestFramework/TestAsset.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
namespace Microsoft.NET.TestFramework
1515
{
16+
/// <summary>
17+
/// A directory wrapper around the <see cref="TestProject"/> class, or any other TestAsset type.
18+
/// It manages the on-disk files of the test asset and provides additional functionality to edit projects.
19+
/// </summary>
1620
public class TestAsset : TestDirectory
1721
{
1822
private readonly string _testAssetRoot;
@@ -21,6 +25,11 @@ public class TestAsset : TestDirectory
2125

2226
public string TestRoot => Path;
2327

28+
/// <summary>
29+
/// The hashed test name (so file paths do not become too long) of the TestAsset owning test.
30+
/// Contains the leaf folder name of any particular test's root folder.
31+
/// The hashing occurs in <see cref="TestAssetsManager"/>.
32+
/// </summary>
2433
public readonly string Name;
2534

2635
public ITestOutputHelper Log { get; }

src/Tests/Microsoft.NET.TestFramework/TestAssetsManager.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ public TestAsset CopyTestAsset(
5252
return testAsset;
5353
}
5454

55+
/// <summary>
56+
/// Writes an in-memory test project onto the disk.
57+
/// </summary>
58+
/// <param name="testProject">The testProject used to create a testAsset with.</param>
59+
/// <param name="callingMethod">Defaults to the name of the caller function (presumably the test).
60+
/// Used to prevent file collisions on tests which share the same test project.</param>
61+
/// <param name="identifier">Use this for theories.
62+
/// The Identifier is used to distinguish between theory child tests. Generally it should be created using a combination of all of the theory parameter values.
63+
/// This is distinct from the test project name and is used to prevent file collisions between theory tests that use the same test project.</param>
64+
/// <param name="targetExtension">The extension type of the desired test project, e.g. .csproj, or .fsproj.</param>
65+
/// <returns>A new TestAsset directory for the TestProject.</returns>
5566
public TestAsset CreateTestProject(
5667
TestProject testProject,
5768
[CallerMemberName] string callingMethod = "",

0 commit comments

Comments
 (0)