Skip to content

Commit 5098d45

Browse files
authored
Disable multi-level lookup by default (dotnet#67022)
1 parent 32320b1 commit 5098d45

37 files changed

+545
-377
lines changed

docs/design/features/framework-version-resolution.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Framework version resolution
22

33
This document describes .NET Core 3.0 version resolution behavior when the host resolves framework references for framework dependent apps.
4-
It's just a part of the overall framework resolution scenario described in [multilevel-sharedfx-lookup](multilevel-sharedfx-lookup.md).
4+
It's just a part of the overall framework resolution scenario described in [sharedfx-lookup](sharedfx-lookup.md).
55

66
## Framework references
77
Application defines its framework dependencies in its `.runtimeconfig.json` file. Each framework then defines its dependencies in its copy of `.runtimeconfig.json`. Each dependency is expressed as a framework reference. Together these form a graph. The host must resolve the references by finding the actual frameworks which are available on the machine. It must also unify references if there are multi references to the same framework.

docs/design/features/host-components.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ The entry-point typically does just one thing: it finds the `hostfxr` library an
2020
## Host FXR
2121
This library finds and resolves the runtime and all the frameworks the app needs. Then it loads the `hostpolicy` library and transfers control to it.
2222

23-
The host FXR library reads the `.runtimeconfig.json` of the app (and all it's dependent frameworks) and resolves the frameworks. It implements the algorithm for framework resolution as described in [SharedFX Lookup](multilevel-sharedfx-lookup.md) and in [Framework version resolution](framework-version-resolution.md).
23+
The host FXR library reads the `.runtimeconfig.json` of the app (and all it's dependent frameworks) and resolves the frameworks. It implements the algorithm for framework resolution as described in [SharedFX Lookup](sharedfx-lookup.md) and in [Framework version resolution](framework-version-resolution.md).
2424

2525
In most cases the latest available version of `hostfxr` is used. Self-contained apps use `hostfxr` from the app folder.
2626

docs/design/features/host-probing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ The list of probing paths ordered according to their priority. First path in the
4848
If the app (or framework) has dependencies on frameworks, these frameworks are used as probing paths.
4949
The order is from the higher level framework to lower level framework. The app is considered the highest level, it direct dependencies are next and so on.
5050
For assets from frameworks, only that framework and lower level frameworks are considered.
51-
Note: These directories come directly out of the framework resolution process. Special note on Windows where global locations are always considered even if the app is not executed via the shared `dotnet.exe`. More details can be found in [Multi-level Shared FX Lookup](multilevel-sharedfx-lookup.md).
51+
Note: These directories come directly out of the framework resolution process. Special note on Windows where global locations are always considered even if the app is not executed via the shared `dotnet.exe`. More details can be found in [Shared FX Lookup](sharedfx-lookup.md).
5252
* Shared store paths
5353
* `$DOTNET_SHARED_STORE/|arch|/|tfm|` - The environment variable `DOTNET_SHARED_STORE` can contain multiple paths, in which case each is appended with `|arch|/|tfm|` and used as a probing path.
5454
* If the app is executed through `dotnet.exe` then path relative to the directory with the `dotnet.exe` is used

docs/design/features/multilevel-sharedfx-lookup.md renamed to docs/design/features/sharedfx-lookup.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# Multi-level SharedFX Lookup
1+
# SharedFX Lookup
22

33
## Introduction
44

5-
There are two possible ways of running .NET Core Applications: through dotnet.exe or through a custom executable appname.exe. The first one is used when the user wants to run a framework-dependent app or a .NET Core command while the second one is used for self-contained applications. Both executables share exactly the same source code.
5+
There are two main ways of running .NET Applications: through `dotnet` or through the `apphost` executables. The executable is in charge of finding and loading `hostfxr`. `hostfxr`, in turn, must find and load `hostpolicy`. It is also responsible for searching for the SDK when running .NET SDK commands. Finally, `hostpolicy` must find and load the runtime (`coreclr`). See [host components](host-components.md) for details.
66

7-
The executable is in charge of finding and loading the hostfxr.dll file. The hostfxr, in turn, must find and load the hostpolicy.dll file (it’s also responsible for searching for the SDK when running .NET commands). At last the coreclr.dll file must be found and loaded by the hostpolicy. Self-contained apps are supposed to keep all its dependencies in the same location as the executable. Framework-dependent apps must have the runtime files inside predefined folders.
7+
An application can either be [framework-dependent](https://docs.microsoft.com/dotnet/core/deploying/#publish-framework-dependent) or [self-contained](https://docs.microsoft.com/dotnet/core/deploying/#publish-self-contained). Framework-dependent apps must have the runtime files inside predefined folders. Self-contained apps are expected to have their dependencies in the same location as the executable.
88

99
## Semantic Versioning
1010

@@ -219,6 +219,16 @@ In order to compare versions of an assembly, the assemblyVersion and fileVersion
219219

220220
## Global locations
221221

222+
Global install locations are described in the [install locations design](https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md) document.
223+
224+
**.NET 7.0 and above**
225+
226+
When running `dotnet`, only the executable directory will be searched and global locations are not searched. For all other [entry-point hosts](host-components.md#entry-point-hosts), if the `DOTNET_ROOT` environment variable is set, that path is searched. If the environment variable is not set, the global location as described in [install locations](https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md) is searched.
227+
228+
See [disable multi-level lookup](https://github.com/dotnet/designs/blob/main/accepted/2022/disable-multi-level-lookup-by-default.md) for more details.
229+
230+
**Before .NET 7.0**
231+
222232
In addition to searching the executable directory, the global .NET location is also searched. The global folders may vary depending on the running operational system. They are defined as follows:
223233

224234
Global .NET location:
@@ -271,11 +281,19 @@ To make sure that the changes are working correctly, the following behavior cond
271281

272282
### SDK search
273283

274-
Like the Framework search, the SDK is searched for a compatible version. Instead of looking for it only in relation to the executable directory, it is also searched in the folders specified above by following the same priority rank.
284+
Like the Framework search, the SDK is searched for a compatible version.
285+
286+
Unlike the Framework search, the SDK search does a roll-forward for pre-release versions when the patch version changes. For example, if you install v2.0.1-pre, it will be used over v2.0.0.
287+
288+
**.NET 7.0 and above**
289+
290+
Only the executable directory will be searched. See [disable multi-level lookup](https://github.com/dotnet/designs/blob/main/accepted/2022/disable-multi-level-lookup-by-default.md) for more details.
291+
292+
**Before .NET 7.0**
293+
294+
Aside from looking for it in relation to the executable directory, it is also searched in the folders specified above by following the same priority rank.
275295

276296
The search is conducted as follows:
277297

278298
1. In relation to the executable directory: search for the specified version. If it cannot be found, choose the most appropriate available version. If there’s no available version, proceed to the next step.
279299
2. In relation to the global location: search for the specified version. If it cannot be found, choose the most appropriate available version. If there’s no available version, then we were not able to find any version folder and an error message is returned.
280-
281-
Unlike the Framework search, the SDK search does a roll-forward for pre-release versions when the patch version changes. For example, if you install v2.0.1-pre, it will be used over v2.0.0.

src/installer/tests/HostActivation.Tests/DotnetArgValidation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public void InvalidFileOrCommand_NoSDK_ListsPossibleIssues()
7979
.Execute(fExpectedToFail: true)
8080
.Should().Fail()
8181
.And.HaveStdErrContaining($"The application '{fileName}' does not exist")
82-
.And.HaveStdErrContaining($"It was not possible to find any installed .NET SDKs");
82+
.And.FindAnySdk(false);
8383
}
8484

8585
// Return a non-exisitent path that contains a mix of / and \

src/installer/tests/HostActivation.Tests/FrameworkResolution/FrameworkResolutionCommandResultExtensions.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,26 +35,32 @@ public static AndConstraint<CommandResultAssertions> ShouldHaveResolvedFramework
3535
/// <returns>Constraint</returns>
3636
public static AndConstraint<CommandResultAssertions> ShouldHaveResolvedFrameworkOrFailToFind(this CommandResult result, string resolvedFrameworkName, string resolvedFrameworkVersion, string resolvedFrameworkBasePath = null)
3737
{
38-
if (resolvedFrameworkName == null || resolvedFrameworkVersion == null ||
38+
if (resolvedFrameworkName == null || resolvedFrameworkVersion == null ||
3939
resolvedFrameworkVersion == FrameworkResolutionBase.ResolvedFramework.NotFound)
4040
{
41-
return result.ShouldFailToFindCompatibleFrameworkVersion();
41+
return result.ShouldFailToFindCompatibleFrameworkVersion(resolvedFrameworkName);
4242
}
4343
else
4444
{
4545
return result.ShouldHaveResolvedFramework(resolvedFrameworkName, resolvedFrameworkVersion, resolvedFrameworkBasePath);
4646
}
4747
}
4848

49-
public static AndConstraint<CommandResultAssertions> DidNotFindCompatibleFrameworkVersion(this CommandResultAssertions assertion)
49+
public static AndConstraint<CommandResultAssertions> DidNotFindCompatibleFrameworkVersion(this CommandResultAssertions assertion, string frameworkName, string requestedVersion)
5050
{
51-
return assertion.HaveStdErrContaining("It was not possible to find any compatible framework version");
51+
var constraint = assertion.HaveStdErrContaining("You must install or update .NET to run this application.");
52+
if (frameworkName is not null)
53+
{
54+
constraint = constraint.And.HaveStdErrContaining($"Framework: '{frameworkName}', {(requestedVersion is null ? "" : $"version '{requestedVersion}'")}");
55+
}
56+
57+
return constraint;
5258
}
5359

54-
public static AndConstraint<CommandResultAssertions> ShouldFailToFindCompatibleFrameworkVersion(this CommandResult result)
60+
public static AndConstraint<CommandResultAssertions> ShouldFailToFindCompatibleFrameworkVersion(this CommandResult result, string frameworkName, string requestedVersion = null)
5561
{
5662
return result.Should().Fail()
57-
.And.DidNotFindCompatibleFrameworkVersion();
63+
.And.DidNotFindCompatibleFrameworkVersion(frameworkName, requestedVersion);
5864
}
5965

6066
public static AndConstraint<CommandResultAssertions> FailedToReconcileFrameworkReference(
@@ -96,7 +102,7 @@ public static AndConstraint<CommandResultAssertions> ShouldHaveResolvedFramework
96102
}
97103
else if (resolvedVersion == FrameworkResolutionBase.ResolvedFramework.NotFound)
98104
{
99-
return result.ShouldFailToFindCompatibleFrameworkVersion();
105+
return result.ShouldFailToFindCompatibleFrameworkVersion(frameworkName, null);
100106
}
101107
else
102108
{

src/installer/tests/HostActivation.Tests/FrameworkResolution/MultipleHives.cs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public MultipleHives(SharedTestState sharedState)
2525
[Theory]
2626
// MLL where global hive has a better match
2727
[InlineData("5.0.0", "net5.0", true, "5.1.2")]
28-
[InlineData("5.0.0", "net5.0", null, "5.1.2")] // MLL is on by default, so same as true
28+
[InlineData("5.0.0", "net5.0", null, "5.1.2")] // MLL is on by default before 7.0, so same as true
2929
[InlineData("5.0.0", "net5.0", false, "5.2.0")] // No global hive allowed
3030
// MLL (where global hive has better match) with various TFMs
3131
[InlineData("5.0.0", "netcoreapp3.0", true, "5.1.2")]
@@ -37,11 +37,12 @@ public MultipleHives(SharedTestState sharedState)
3737
[InlineData("5.0.0", "net6.0", true, "5.1.2")]
3838
[InlineData("5.0.0", "net6.0", null, "5.1.2")]
3939
[InlineData("5.0.0", "net6.0", false, "5.2.0")]
40-
[InlineData("7.0.0", "net7.0", true, "7.0.1")]
41-
[InlineData("7.0.0", "net7.0", null, "7.0.1")]
40+
// MLL is disabled for 7.0+
41+
[InlineData("7.0.0", "net7.0", true, "7.1.2")] // MLL disabled for 7.0+ - setting it doesn't change anything
42+
[InlineData("7.0.0", "net7.0", null, "7.1.2")]
4243
[InlineData("7.0.0", "net7.0", false, "7.1.2")]
43-
[InlineData("7.0.0", "net8.0", true, "7.0.1")]
44-
[InlineData("7.0.0", "net8.0", null, "7.0.1")]
44+
[InlineData("7.0.0", "net8.0", true, "7.1.2")] // MLL disabled for 7.0+ - setting it doesn't change anything
45+
[InlineData("7.0.0", "net8.0", null, "7.1.2")]
4546
[InlineData("7.0.0", "net8.0", false, "7.1.2")]
4647
// MLL where main hive has a better match
4748
[InlineData("6.0.0", "net6.0", true, "6.1.4")] // Global hive with better version (higher patch)
@@ -82,7 +83,7 @@ public void FrameworkHiveSelection_CurrentDirectoryIsIgnored()
8283
[InlineData("6.1.4", "net6.0", true, "6.1.4", true)]
8384
[InlineData("6.1.4", "net6.0", null, "6.1.4", true)]
8485
[InlineData("6.1.4", "net6.0", false, ResolvedFramework.NotFound, false)]
85-
[InlineData("6.1.4", "net7.0", true, "6.1.4", true)]
86+
[InlineData("6.1.4", "net7.0", true, ResolvedFramework.NotFound, false)] // MLL disabled for 7.0+
8687
[InlineData("7.1.2", "net6.0", true, "7.1.2", false)] // 7.1.2 is in both main and global hives - the main should always win with exact match
8788
[InlineData("7.1.2", "net6.0", null, "7.1.2", false)]
8889
[InlineData("7.1.2", "net6.0", false, "7.1.2", false)]
@@ -154,7 +155,7 @@ public void ListRuntimes(bool? multiLevelLookup)
154155

155156
string expectedOutput = string.Join(
156157
string.Empty,
157-
GetExpectedFrameworks(multiLevelLookup)
158+
GetExpectedFrameworks(false) // MLL Is always disabled for dotnet --list-runtimes
158159
.Select(t => $"{MicrosoftNETCoreApp} {t.Version} [{Path.Combine(t.Path, "shared", MicrosoftNETCoreApp)}]{Environment.NewLine}"));
159160

160161
// !!IMPORTANT!!: This test verifies the exact match of the entire output of the command (not a substring!)
@@ -179,7 +180,7 @@ public void DotnetInfo(bool? multiLevelLookup)
179180
string expectedOutput =
180181
$".NET runtimes installed:{Environment.NewLine}" +
181182
string.Join(string.Empty,
182-
GetExpectedFrameworks(multiLevelLookup)
183+
GetExpectedFrameworks(false) // MLL is always disabled for dotnet --info
183184
.Select(t => $" {MicrosoftNETCoreApp} {t.Version} [{Path.Combine(t.Path, "shared", MicrosoftNETCoreApp)}]{Environment.NewLine}"));
184185

185186
RunTest(
@@ -190,13 +191,14 @@ public void DotnetInfo(bool? multiLevelLookup)
190191
}
191192

192193
[Theory]
193-
[InlineData("net5.0", true)]
194-
[InlineData("net5.0", null)]
195-
[InlineData("net5.0", false)]
196-
[InlineData("net7.0", true)]
197-
[InlineData("net7.0", null)]
198-
[InlineData("net7.0", false)]
199-
public void FrameworkResolutionError(string tfm, bool? multiLevelLookup)
194+
[InlineData("net5.0", true, true)]
195+
[InlineData("net5.0", null, true)]
196+
[InlineData("net5.0", false, false)]
197+
// MLL is disabled for 7.0+
198+
[InlineData("net7.0", true, false)]
199+
[InlineData("net7.0", null, false)]
200+
[InlineData("net7.0", false, false)]
201+
public void FrameworkResolutionError(string tfm, bool? multiLevelLookup, bool effectiveMultiLevelLookup)
200202
{
201203
// Multi-level lookup is only supported on Windows.
202204
if (!OperatingSystem.IsWindows() && multiLevelLookup != false)
@@ -205,16 +207,17 @@ public void FrameworkResolutionError(string tfm, bool? multiLevelLookup)
205207
string expectedOutput =
206208
$"The following frameworks were found:{Environment.NewLine}" +
207209
string.Join(string.Empty,
208-
GetExpectedFrameworks(multiLevelLookup)
209-
.Select(t => $" {t.Version} at [{Path.Combine(t.Path, "shared", MicrosoftNETCoreApp)}]{Environment.NewLine}"));
210+
GetExpectedFrameworks(effectiveMultiLevelLookup)
211+
.Select(t => $" {t.Version} at [{Path.Combine(t.Path, "shared", MicrosoftNETCoreApp)}]{Environment.NewLine}"));
210212

211213
RunTest(
212214
runtimeConfig => runtimeConfig
213215
.WithTfm(tfm)
214216
.WithFramework(MicrosoftNETCoreApp, "9999.9.9"),
215217
multiLevelLookup)
216218
.Should().Fail()
217-
.And.HaveStdErrContaining(expectedOutput);
219+
.And.HaveStdErrContaining(expectedOutput)
220+
.And.HaveStdErrContaining("https://aka.ms/dotnet/app-launch-failed");
218221
}
219222

220223
private CommandResult RunTest(Func<RuntimeConfig, RuntimeConfig> runtimeConfig, bool? multiLevelLookup = true)

src/installer/tests/HostActivation.Tests/FrameworkResolution/RollForwardAndRollForwardOnNoCandidateFxSettings.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,11 @@ public void Precedence(
101101
SettingLocation rollForwardOnNoCandidateFxLocation,
102102
bool passes)
103103
{
104+
string requestedVersion = "5.0.0";
104105
CommandResult result = RunTest(
105106
new TestSettings()
106107
.WithRuntimeConfigCustomizer(runtimeConfig => runtimeConfig
107-
.WithFramework(MicrosoftNETCoreApp, "5.0.0"))
108+
.WithFramework(MicrosoftNETCoreApp, requestedVersion))
108109
.With(RollForwardSetting(rollForwardLocation, Constants.RollForwardSetting.Major))
109110
.With(RollForwardOnNoCandidateFxSetting(rollForwardOnNoCandidateFxLocation, 0)));
110111

@@ -114,7 +115,7 @@ public void Precedence(
114115
}
115116
else
116117
{
117-
result.Should().Fail().And.DidNotFindCompatibleFrameworkVersion();
118+
result.ShouldFailToFindCompatibleFrameworkVersion(MicrosoftNETCoreApp, requestedVersion);
118119
}
119120
}
120121

0 commit comments

Comments
 (0)