From 53f8e1e0d8399ea482c90c457fb11ed8635f32f1 Mon Sep 17 00:00:00 2001 From: masamichi Date: Fri, 24 Apr 2026 22:16:50 +0900 Subject: [PATCH 1/2] Fix shared worker runtime selection Disable .NET multilevel lookup for the shared Roslyn worker process so Unity's bundled runtime is used consistently with the references selected during worker compilation. Add coverage for the worker runtime environment setting to guard against regressions. --- .../SharedRoslynCompilerWorkerHostTests.cs | 22 +++++++++++++++++++ ...haredRoslynCompilerWorkerHostTests.cs.meta | 11 ++++++++++ .../SharedRoslynCompilerWorkerHost.cs | 17 +++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs create mode 100644 Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs.meta diff --git a/Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs b/Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs new file mode 100644 index 000000000..59ca88cf2 --- /dev/null +++ b/Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs @@ -0,0 +1,22 @@ +using System.Diagnostics; +using NUnit.Framework; + +namespace io.github.hatayama.uLoopMCP.DynamicCodeToolTests +{ + [TestFixture] + public class SharedRoslynCompilerWorkerHostTests + { + [Test] + public void ConfigureWorkerDotnetRuntimeEnvironment_WhenCalled_ShouldDisableMultilevelLookup() + { + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.EnvironmentVariables[SharedRoslynCompilerWorkerHost.DotnetMultilevelLookupEnvironmentVariableName] = "1"; + + SharedRoslynCompilerWorkerHost.ConfigureWorkerDotnetRuntimeEnvironment(startInfo); + + Assert.That( + startInfo.EnvironmentVariables[SharedRoslynCompilerWorkerHost.DotnetMultilevelLookupEnvironmentVariableName], + Is.EqualTo(SharedRoslynCompilerWorkerHost.DotnetMultilevelLookupDisabledValue)); + } + } +} diff --git a/Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs.meta b/Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs.meta new file mode 100644 index 000000000..a6d91520c --- /dev/null +++ b/Assets/Tests/Editor/DynamicCodeToolTests/SharedRoslynCompilerWorkerHostTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c8b49813482e2c4488d3be1f909b241e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/Compilation/SharedRoslynCompilerWorkerHost.cs b/Packages/src/Editor/Compilation/SharedRoslynCompilerWorkerHost.cs index 648e54982..45f862de0 100644 --- a/Packages/src/Editor/Compilation/SharedRoslynCompilerWorkerHost.cs +++ b/Packages/src/Editor/Compilation/SharedRoslynCompilerWorkerHost.cs @@ -21,6 +21,8 @@ internal static class SharedRoslynCompilerWorkerHost private const string RoslynWorkerCompileResponseFileName = "RoslynCompilerWorker.rsp"; private const int SharedCompilerWorkerResponseTimeoutMilliseconds = 30000; private const int WorkerAssemblyBuildTimeoutMilliseconds = 30000; + internal const string DotnetMultilevelLookupEnvironmentVariableName = "DOTNET_MULTILEVEL_LOOKUP"; + internal const string DotnetMultilevelLookupDisabledValue = "0"; private static readonly object SharedCompilerWorkerLock = new(); private static Action s_deleteWorkerDirectory = path => Directory.Delete(path, true); @@ -486,7 +488,7 @@ private static ProcessStartInfo CreateWorkerStartInfo( ExternalCompilerPaths externalCompilerPaths, WorkerPaths workerPaths) { - return new ProcessStartInfo + ProcessStartInfo startInfo = new ProcessStartInfo { FileName = externalCompilerPaths.DotnetHostPath, Arguments = "exec" @@ -500,6 +502,19 @@ private static ProcessStartInfo CreateWorkerStartInfo( RedirectStandardError = false, CreateNoWindow = true }; + + ConfigureWorkerDotnetRuntimeEnvironment(startInfo); + return startInfo; + } + + internal static void ConfigureWorkerDotnetRuntimeEnvironment(ProcessStartInfo startInfo) + { + Debug.Assert(startInfo != null, "startInfo must not be null"); + + // Why: global probing can select a system .NET 6 runtime while Unity 6000.4 worker + // references come from the bundled .NET 8 runtime, which breaks assembly binding. + startInfo.EnvironmentVariables[DotnetMultilevelLookupEnvironmentVariableName] = + DotnetMultilevelLookupDisabledValue; } private static WorkerAssemblyBuildResult CompileWorkerAssembly( From 8c4adf8238cdb66e02af25dd4a987b22637ef4ff Mon Sep 17 00:00:00 2001 From: masamichi Date: Fri, 24 Apr 2026 22:41:48 +0900 Subject: [PATCH 2/2] Apply runtime isolation to worker build Use the same DOTNET_MULTILEVEL_LOOKUP guard when launching the Roslyn compiler that builds the shared worker, so the initial worker build cannot select a system runtime before Unity's bundled runtime. --- .../src/Editor/Compilation/SharedRoslynCompilerWorkerHost.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Packages/src/Editor/Compilation/SharedRoslynCompilerWorkerHost.cs b/Packages/src/Editor/Compilation/SharedRoslynCompilerWorkerHost.cs index 45f862de0..b2ef42e74 100644 --- a/Packages/src/Editor/Compilation/SharedRoslynCompilerWorkerHost.cs +++ b/Packages/src/Editor/Compilation/SharedRoslynCompilerWorkerHost.cs @@ -549,6 +549,7 @@ private static WorkerAssemblyBuildResult CompileWorkerAssembly( RedirectStandardError = true, CreateNoWindow = true }; + ConfigureWorkerDotnetRuntimeEnvironment(startInfo); using Process process = ProcessStartHelper.TryStart(startInfo); if (process == null)