Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Project>
<Import Project="Version.Details.props" Condition="Exists('Version.Details.props')" />
<PropertyGroup>
<VersionPrefix>18.0.1</VersionPrefix><DotNetFinalVersionKind>release</DotNetFinalVersionKind>
<VersionPrefix>18.0.2</VersionPrefix><DotNetFinalVersionKind>release</DotNetFinalVersionKind>
<PackageValidationBaselineVersion>17.14.8</PackageValidationBaselineVersion>
<AssemblyVersion>15.1.0.0</AssemblyVersion>
<PreReleaseVersionLabel>servicing</PreReleaseVersionLabel>
Expand Down
3 changes: 3 additions & 0 deletions src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@
<EmbeddedResource Include="TestAssets\ExampleNetTask\TestMSBuildTaskInNet\global.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="TestAssets\ExampleNetTask\TestNetTaskWithImplicitParams\global.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="TestAssets\ExampleNetTask\TestNetTask\global.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
Expand Down
21 changes: 21 additions & 0 deletions src/Build.UnitTests/NetTaskHost_E2E_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,26 @@ public void MSBuildTaskInNetHostTest()
successTestTask.ShouldBeTrue();
testTaskOutput.ShouldContain($"Hello TEST");
}

[WindowsFullFrameworkOnlyFact]
public void NetTaskWithImplicitHostParamsTest()
{
using TestEnvironment env = TestEnvironment.Create(_output);
var dotnetPath = env.GetEnvironmentVariable("DOTNET_ROOT");

string testProjectPath = Path.Combine(TestAssetsRootPath, "ExampleNetTask", "TestNetTaskWithImplicitParams", "TestNetTaskWithImplicitParams.csproj");

string testTaskOutput = RunnerUtilities.ExecBootstrapedMSBuild($"{testProjectPath} -restore -v:n", out bool successTestTask);

if (!successTestTask)
{
_output.WriteLine(testTaskOutput);
}

successTestTask.ShouldBeTrue();
testTaskOutput.ShouldContain($"The task is executed in process: dotnet");
testTaskOutput.ShouldContain($"Process path: {dotnetPath}", customMessage: testTaskOutput);
testTaskOutput.ShouldContain("/nodereuse:False");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
<TestProjectFolder>$([System.IO.Path]::GetFullPath('$([System.IO.Path]::Combine('$(AssemblyLocation)', '..'))'))</TestProjectFolder>
<ExampleTaskPath>$([System.IO.Path]::Combine('$(TestProjectFolder)', '$(TargetFramework)', 'ExampleTask.dll'))</ExampleTaskPath>
</PropertyGroup>

<UsingTask TaskName="ExampleTask" AssemblyFile="$(ExampleTaskPath)" Runtime="NET"/>

<Target Name="TestTask" BeforeTargets="Build">
<ExampleTask />
</Target>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"sdk": {
// This global.json is needed to prevent builds running in tests using the bootstrap layout from walking
// up the repo tree and resolving our sdk.paths, instead of the bootstrap layout's SDK.
// See https://github.com/dotnet/runtime/issues/118488 for details.
"allowPrerelease": true,
"rollForward": "latestMajor"
}
}
7 changes: 5 additions & 2 deletions src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,16 +274,19 @@ internal LoadedType InitializeFactory(
ErrorUtilities.VerifyThrowArgumentNull(loadInfo);
VerifyThrowIdentityParametersValid(taskFactoryIdentityParameters, elementLocation, taskName, "Runtime", "Architecture");

bool taskHostParamsMatchCurrentProc = true;
if (taskFactoryIdentityParameters != null)
{
taskHostParamsMatchCurrentProc = TaskHostParametersMatchCurrentProcess(taskFactoryIdentityParameters);
_factoryIdentityParameters = new Dictionary<string, string>(taskFactoryIdentityParameters, StringComparer.OrdinalIgnoreCase);
}

_taskHostFactoryExplicitlyRequested = taskHostExplicitlyRequested;

_isTaskHostFactory = (taskFactoryIdentityParameters != null
&& taskFactoryIdentityParameters.TryGetValue(Constants.TaskHostExplicitlyRequested, out string isTaskHostFactory)
&& isTaskHostFactory.Equals("true", StringComparison.OrdinalIgnoreCase));
&& isTaskHostFactory.Equals("true", StringComparison.OrdinalIgnoreCase))
|| !taskHostParamsMatchCurrentProc;

try
{
Expand All @@ -293,7 +296,7 @@ internal LoadedType InitializeFactory(
string assemblyName = loadInfo.AssemblyName ?? Path.GetFileName(loadInfo.AssemblyFile);
using var assemblyLoadsTracker = AssemblyLoadsTracker.StartTracking(targetLoggingContext, AssemblyLoadingContext.TaskRun, assemblyName);

_loadedType = _typeLoader.Load(taskName, loadInfo, _taskHostFactoryExplicitlyRequested);
_loadedType = _typeLoader.Load(taskName, loadInfo, _taskHostFactoryExplicitlyRequested, taskHostParamsMatchCurrentProc);
ProjectErrorUtilities.VerifyThrowInvalidProject(_loadedType != null, elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, String.Empty);
}
catch (TargetInvocationException e)
Expand Down
8 changes: 6 additions & 2 deletions src/Build/Instance/TaskRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1489,8 +1489,12 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo

bool isAssemblyTaskFactory = String.Equals(TaskFactoryAttributeName, AssemblyTaskFactory, StringComparison.OrdinalIgnoreCase);
bool isTaskHostFactory = String.Equals(TaskFactoryAttributeName, TaskHostFactory, StringComparison.OrdinalIgnoreCase);
_taskFactoryParameters ??= new();
TaskFactoryParameters.Add(Constants.TaskHostExplicitlyRequested, isTaskHostFactory.ToString());
_taskFactoryParameters ??= [];

if (isTaskHostFactory)
{
TaskFactoryParameters.Add(Constants.TaskHostExplicitlyRequested, isTaskHostFactory.ToString());
}

if (isAssemblyTaskFactory || isTaskHostFactory)
{
Expand Down
28 changes: 21 additions & 7 deletions src/Shared/TypeLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,10 @@ private static Assembly LoadAssemblyUsingMetadataLoadContext(AssemblyLoadInfo as
internal LoadedType Load(
string typeName,
AssemblyLoadInfo assembly,
bool useTaskHost = false)
bool useTaskHost = false,
bool taskHostParamsMatchCurrentProc = true)
{
return GetLoadedType(s_cacheOfLoadedTypesByFilter, typeName, assembly, useTaskHost);
return GetLoadedType(s_cacheOfLoadedTypesByFilter, typeName, assembly, useTaskHost, taskHostParamsMatchCurrentProc);
}

/// <summary>
Expand All @@ -227,15 +228,20 @@ internal LoadedType ReflectionOnlyLoad(
string typeName,
AssemblyLoadInfo assembly)
{
return GetLoadedType(s_cacheOfReflectionOnlyLoadedTypesByFilter, typeName, assembly, useTaskHost: false);
return GetLoadedType(s_cacheOfReflectionOnlyLoadedTypesByFilter, typeName, assembly, useTaskHost: false, taskHostParamsMatchCurrentProc: true);
}

/// <summary>
/// Loads the specified type if it exists in the given assembly. If the type name is fully qualified, then a match (if
/// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type
/// found will be returned.
/// </summary>
private LoadedType GetLoadedType(ConcurrentDictionary<Func<Type, object, bool>, ConcurrentDictionary<AssemblyLoadInfo, AssemblyInfoToLoadedTypes>> cache, string typeName, AssemblyLoadInfo assembly, bool useTaskHost)
private LoadedType GetLoadedType(
ConcurrentDictionary<Func<Type, object, bool>, ConcurrentDictionary<AssemblyLoadInfo, AssemblyInfoToLoadedTypes>> cache,
string typeName,
AssemblyLoadInfo assembly,
bool useTaskHost,
bool taskHostParamsMatchCurrentProc)
{
// A given type filter have been used on a number of assemblies, Based on the type filter we will get another dictionary which
// will map a specific AssemblyLoadInfo to a AssemblyInfoToLoadedTypes class which knows how to find a typeName in a given assembly.
Expand All @@ -246,7 +252,7 @@ private LoadedType GetLoadedType(ConcurrentDictionary<Func<Type, object, bool>,
AssemblyInfoToLoadedTypes typeNameToType =
loadInfoToType.GetOrAdd(assembly, (_) => new AssemblyInfoToLoadedTypes(_isDesiredType, _));

return typeNameToType.GetLoadedTypeByTypeName(typeName, useTaskHost);
return typeNameToType.GetLoadedTypeByTypeName(typeName, useTaskHost, taskHostParamsMatchCurrentProc);
}

/// <summary>
Expand Down Expand Up @@ -316,11 +322,11 @@ internal AssemblyInfoToLoadedTypes(Func<Type, object, bool> typeFilter, Assembly
/// <summary>
/// Determine if a given type name is in the assembly or not. Return null if the type is not in the assembly
/// </summary>
internal LoadedType GetLoadedTypeByTypeName(string typeName, bool useTaskHost)
internal LoadedType GetLoadedTypeByTypeName(string typeName, bool useTaskHost, bool taskHostParamsMatchCurrentProc)
{
ErrorUtilities.VerifyThrowArgumentNull(typeName);

if (useTaskHost && _assemblyLoadInfo.AssemblyFile is not null)
if (ShouldUseMetadataLoadContext(useTaskHost, taskHostParamsMatchCurrentProc))
{
return GetLoadedTypeFromTypeNameUsingMetadataLoadContext(typeName);
}
Expand Down Expand Up @@ -374,6 +380,14 @@ internal LoadedType GetLoadedTypeByTypeName(string typeName, bool useTaskHost)
return type != null ? new LoadedType(type, _assemblyLoadInfo, _loadedAssembly ?? type.Assembly, typeof(ITaskItem), loadedViaMetadataLoadContext: false) : null;
}

/// <summary>
/// Determine whether an assembly is likely to be used out of process and thus loaded with a <see cref="MetadataLoadContext"/>.
/// </summary>
/// <param name="useTaskHost">Task Host Parameter was specified explicitly in XML or through environment variable.</param>
/// <param name="taskHostParamsMatchCurrentProc">The parameter defines if Runtime/Architecture explicitly defined in XML match current process.</param>
private bool ShouldUseMetadataLoadContext(bool useTaskHost, bool taskHostParamsMatchCurrentProc) =>
(useTaskHost || !taskHostParamsMatchCurrentProc) && _assemblyLoadInfo.AssemblyFile is not null;

private LoadedType GetLoadedTypeFromTypeNameUsingMetadataLoadContext(string typeName)
{
return _publicTypeNameToLoadedType.GetOrAdd(typeName, typeName =>
Expand Down
5 changes: 0 additions & 5 deletions src/UnitTests.Shared/TestEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,6 @@ public partial class TestEnvironment : IDisposable
/// (MSBuild_*.txt) in the temp directory and treats their presence as test failures.
/// Set to true to disable this monitoring for tests that expect build failures.
/// </param>
/// <param name="setupDotnetEnvVars">
/// When true, configures .NET-specific environment variables including PATH,
/// DOTNET_ROOT, and DOTNET_HOST_PATH to point to the bootstrap .NET installation.
/// This ensures tests use the correct .NET runtime and SDK versions.
/// </param>
/// <returns>
/// A configured TestEnvironment instance with the specified settings applied.
/// </returns>
Expand Down