Skip to content

Set APP_CONTEXT_BASE_DIRECTORY on CoreCLR and NativeAOT - #12282

Open
jonathanpeppers wants to merge 2 commits into
jonathanpeppers-special-barnaclefrom
jonathanpeppers-app-context-base-directory
Open

Set APP_CONTEXT_BASE_DIRECTORY on CoreCLR and NativeAOT#12282
jonathanpeppers wants to merge 2 commits into
jonathanpeppers-special-barnaclefrom
jonathanpeppers-app-context-base-directory

Conversation

@jonathanpeppers

@jonathanpeppers jonathanpeppers commented Jul 31, 2026

Copy link
Copy Markdown
Member

Note

This is a stacked PR. Its base is jonathanpeppers-special-barnacle, not main — it builds on top of #12278 ("[CoreCLR] Set the RUNTIME_IDENTIFIER runtime property"). Review/merge #12278 first.

Why this change is necessary

AppContext.BaseDirectory is backed by the APP_CONTEXT_BASE_DIRECTORY host property:

public static string BaseDirectory =>
    GetData("APP_CONTEXT_BASE_DIRECTORY") as string ??
    (s_defaultBaseDirectory ??= GetBaseDirectoryCore());

On other platforms hostfxr supplies that property. .NET for Android does not use hostfxr, so our native host has to supply it itself. MonoVM has always done this (src/native/mono/monodroid/monovm-properties.{cc,hh}), but CoreCLR and NativeAOT never did, so both silently fell through to GetBaseDirectoryCore (), which returns something useless on Android:

Runtime Fallback used Value before this change
CoreCLR Path.GetDirectoryName (Assembly.GetEntryAssembly ()?.Location) string.Emptymeasured on device with EmbedAssembliesIntoApk=true; assemblies are read straight out of the APK, so Location is empty
NativeAOT Path.GetDirectoryName (Environment.ProcessPath) /system/bin/measured on device by reverting the fix and re-running the test; that is where app_process64 lives
MonoVM n/a, property was set /data/user/0/<pkg>/files (no trailing separator)

The CoreCLR value is also unstable across deployment modes: with Fast Deployment the entry assembly lives on disk under files/.__override__/<arch>/, so the fallback resolves somewhere different than it does for an APK-embedded build. I did not measure the pre-change Fast Deployment value, so treat that as documented GetBaseDirectoryCore () behavior rather than an observed one — either way it is not filesDir. Once the host property is set from filesDir, AppContext.BaseDirectory no longer depends on where assemblies physically live.

What changed

  • CoreCLRAPP_CONTEXT_BASE_DIRECTORY is now a reserved runtime property at index 2, exactly like RUNTIME_IDENTIFIER in [CoreCLR] Set the RUNTIME_IDENTIFIER runtime property #12278. ApplicationConfigNativeAssemblyGeneratorCLR emits the name in the fixed prologue, and Host::Java_mono_android_Runtime_initInternal fills the value in from the filesDir it is already handed by mono.android.Runtime.initInternal. application_dso_stub.cc is kept in sync.
  • NativeAOT — there is no property bag to write into (and no ILC knob for this, unlike RUNTIME_IDENTIFIER), so JavaInteropRuntime.init () calls AppContext.SetData () immediately after the runtime is created, using the filesDir jstring it already receives.
  • MonoVM — appends the trailing directory separator so all three runtimes agree.

All three runtimes now return <Context.getFilesDir ()>/. Everywhere else in .NET, AppContext.BaseDirectory ends with a directory separator, so it does here too; that is a small behavior change for MonoVM apps that string-concatenate instead of using Path.Combine.

Unit tests

SystemTests.AppContextTests.BaseDirectoryIsFilesDir in Mono.Android-Tests asserts the managed value against the independently-obtained Java value, Android.App.Application.Context.FilesDir.AbsolutePath, rather than a hardcoded path.

Verified on a physical arm64-v8a device:

  • CoreCLR, Debug — fails before the fix with Expected: "/data/user/0/Mono.Android.NET_Tests/files/" But was: <string.Empty>, passes after.
  • NativeAOT, Release — fails before the fix with Expected: "/data/user/0/Mono.Android.NET_Tests/files/" But was: "/system/bin/", passes after (631 tests, 0 failures).
  • CoreCLR, Debug, Fast Deployment (assemblies deployed to files/.__override__/arm64-v8a/, confirmed present on device) — passing after the fix, confirming the value no longer depends on assembly location.

MonoVM could not be exercised: NETSDK1242 ("Building Android projects with the Mono runtime is not supported in .NET 11.0 and later") makes the test app unbuildable on main. Its one-line change is by inspection only.

`AppContext.BaseDirectory` is backed by the `APP_CONTEXT_BASE_DIRECTORY`
host property. On other platforms `hostfxr` supplies it, but .NET for
Android does not use `hostfxr`, so our native host has to. MonoVM did
this already; CoreCLR and NativeAOT never did, so both silently fell
back to `AppContext.GetBaseDirectoryCore()`:

* CoreCLR uses `Assembly.GetEntryAssembly ()?.Location`, which is empty
  because assemblies are read straight out of the APK. On device
  `AppContext.BaseDirectory` was the empty string.
* NativeAOT uses `Environment.ProcessPath`, which is
  `/system/bin/app_process64`.

CoreCLR now gets the value the same way as `RUNTIME_IDENTIFIER`: a
reserved runtime property whose value the host fills in, here from the
`filesDir` it is already handed by `mono.android.Runtime.initInternal`.
NativeAOT has no property bag at all, so `JavaInteropRuntime.init()`
calls `AppContext.SetData()` right after the runtime is created.

All three runtimes now terminate the value with a directory separator,
which is what .NET does everywhere else; MonoVM was missing it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cb741000-4630-4850-bd58-aedfbd144254
Copilot AI review requested due to automatic review settings July 31, 2026 18:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR ensures AppContext.BaseDirectory is consistent and meaningful on Android by explicitly setting the APP_CONTEXT_BASE_DIRECTORY host property (or equivalent) across CoreCLR, NativeAOT, and MonoVM, aligning behavior with other .NET platforms and making it deployment-independent.

Changes:

  • CoreCLR: reserves APP_CONTEXT_BASE_DIRECTORY as a runtime property (index 2) and populates it from filesDir during initInternal.
  • NativeAOT: sets AppContext data for APP_CONTEXT_BASE_DIRECTORY from the provided filesDir during runtime init.
  • Tests/MonoVM: adds a regression test for AppContext.BaseDirectory and adjusts MonoVM to ensure a trailing directory separator.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Mono.Android-Tests/Mono.Android-Tests/System/AppContextTests.cs Adds a test asserting AppContext.BaseDirectory matches Context.FilesDir with a trailing separator.
src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs Emits APP_CONTEXT_BASE_DIRECTORY in the fixed prologue runtime property list for CoreCLR.
src/native/mono/monodroid/monovm-properties.hh Ensures MonoVM’s APP_CONTEXT_BASE_DIRECTORY value ends with / for consistency.
src/native/clr/xamarin-app-stub/application_dso_stub.cc Updates CoreCLR stub runtime property arrays/count to include APP_CONTEXT_BASE_DIRECTORY.
src/native/clr/host/host.cc Sets CoreCLR APP_CONTEXT_BASE_DIRECTORY from files_dir and ensures trailing /.
src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs Sets APP_CONTEXT_BASE_DIRECTORY via AppContext.SetData() for NativeAOT after runtime creation.

Comment thread tests/Mono.Android-Tests/Mono.Android-Tests/System/AppContextTests.cs Outdated
Comment thread src/native/clr/host/host.cc Outdated
- `host.cc`: assign the function-local `static std::string` on every call
  instead of relying on its initializer, which would only ever observe the
  first `files_dir` if `initInternal` ever ran again in-process. The static
  is still needed so the storage outlives `coreclr_initialize`.
- `AppContextTests`: normalize the expected value to exactly one trailing
  separator rather than assuming `FilesDir.AbsolutePath` never ends in one.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cb741000-4630-4850-bd58-aedfbd144254
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants