Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle relative path loading on non-Windows for AppContext.BaseDirectory on NativeAOT #113171

Closed
wants to merge 2 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

using Internal.Runtime.Augments;
using System.Collections.Generic;
using System.IO;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Text;

Expand Down Expand Up @@ -40,18 +42,60 @@ internal static void OnUnhandledException(object e)

private static unsafe string GetRuntimeModulePath()
{
// We aren't going to call this method, we just need an address that we know is in this module.
// As this code is NativeAOT only, we know that this method will be AOT compiled into the executable,
// so the entry point address will be in the module.
void* ip = (void*)(delegate*<string>)&GetRuntimeModulePath;
if (RuntimeAugments.TryGetFullPathToApplicationModule((nint)ip, out _) is string modulePath)
return RuntimeModulePathHolder.RuntimeModulePath;
}

internal static class RuntimeModulePathHolder
{
private static string? s_workingDirectoryAtStartup;
private static string WorkingDirectoryAtStartup
{
// In case a user's module initializer tries to get AppContext.BaseDirectory
// before our module initializer has run, we need to set up the getter to initialize
// the value.
get => s_workingDirectoryAtStartup ??= Environment.CurrentDirectory;
}

#pragma warning disable CA2255 // The 'ModuleInitializer' attribute is only intended to be used in application code or advanced source generator scenarios
// We need to capture this value at startup in case the user changes Environment.CurrentDirectory
// before the first execution of AppContext.BaseDirectory.
[ModuleInitializer]
#pragma warning restore CA2255
public static void Initialize()
{
return modulePath;
s_workingDirectoryAtStartup = Environment.CurrentDirectory;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that close to every NAOT app is going to compute current directory during startup now?

}

// If this method isn't in a dynamically loaded module,
// then it's in the executable. In that case, we can use the process path.
return Environment.ProcessPath;
public static string RuntimeModulePath { get; } = GetRuntimeModulePath();

private static unsafe string GetRuntimeModulePath()
{
// We aren't going to call this method, we just need an address that we know is in this module.
// As this code is NativeAOT only, we know that this method will be AOT compiled into the executable,
// so the entry point address will be in the module.
void* ip = (void*)(delegate*<string>)&GetRuntimeModulePath;
if (RuntimeAugments.TryGetFullPathToApplicationModule((nint)ip, out _) is string modulePath)
{
if (!Path.IsPathRooted(modulePath))
{
// If the path is not rooted, it is relative to the working directory.
// We need to make it absolute.
modulePath = Path.Combine(WorkingDirectoryAtStartup, modulePath);
if (!Path.Exists(modulePath))
{
// If this path doesn't exist, that means that this module was loaded
// from a different relative path using something like LD_LIBRARY_PATH.
// In this case, we'll say we can't get the module path and return the process path.
return Environment.ProcessPath;
}
}
return modulePath;
}

// If this method isn't in a dynamically loaded module,
// then it's in the executable. In that case, we can use the process path.
return Environment.ProcessPath;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
using Xunit;

namespace System.Tests
Expand Down Expand Up @@ -28,5 +29,11 @@ public void AppContext_ThrowTest()
{
AssertExtensions.Throws<ArgumentNullException>("name", () => AppContext.SetData(null, 123));
}

[Fact]
public void BaseDirectory_PathRooted()
{
Assert.True(Path.IsPathRooted(AppContext.BaseDirectory), "BaseDirectory should be a rooted path");
}
}
}
Loading