Expose function for locating hostfxr#5522
Conversation
elinor-fung
commented
Mar 21, 2019
- Add nethost library
- Implement nethost_get_hostfxr_path
- Add basic test for (a native host using) nethost
ae108e2 to
a87235d
Compare
5b62024 to
7501fd8
Compare
| bool fxr_resolver::try_get_path(const host_mode_t mode, const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path) | ||
| { | ||
| pal::string_t fxr_dir; | ||
| #if FEATURE_APPHOST || FEATURE_LIBHOST |
There was a problem hiding this comment.
We go to great lengths to avoid breaking changes to the muxer. I wonder if we should keep some #if here. Something like:
#if !FEATURE_MUXEROr maybe it would be better to leave the interface unchanged. Keep most of the original code and interface. I see no significant functional change to this file, just refactoring for cleanup.
bool resolve_fxr_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path)Except two line removed above.
There was a problem hiding this comment.
I think I'm missing what the breaking change is here?
The functionality / logic should be the same. For nethost, we need to be able to resolve using either the muxer behaviour or the apphost/libhost behaviour, so I removed the preprocessor conditions here. But corehost.cpp is setting the mode based on the defines such that the behaviour should be the same (and com/ijwhost, which have FEATURE_LIBHOST, are always using host_mode_t::libhost)
There was a problem hiding this comment.
This isn't an exported function. There is no breaking change here.
There was a problem hiding this comment.
We spent months getting approval to make changes to the muxer. We had to justify changes to
- Add the manifest
- Add logging to file support
- Add support for SemVer 2.0.0
We could logically reason that none of these were breaking.
This PR is introducing a non-functional change to the muxer. I agree it shouldn't be breaking, but it does change muxer code. It is introducing a switch and dead code which was not compiled for the muxer before.
If there was a functional reason for the code change, it is OK to keep the switch. I would still think about restoring an #if to eliminate the dead code for the muxer. I'll let @vitek-karas decide if I am being overly cautious.
There was a problem hiding this comment.
We spent months getting approval to make changes to the muxer.
Not for comhost which had much more impactful functional changes - the very notion of libhost for instance.
There was a problem hiding this comment.
Not for comhost which had much more impactful functional changes - the very notion of libhost for instance.
Only because we didn't mention they affected the muxer. If there are changes to the muxer, we must review them carefully. @jeffschwMSFT wants us to make sure there is sufficient coverage to guarantee the muxer works properly for every shipping .NET Core version.
There was a problem hiding this comment.
Only because we didn't mention they affected the muxer.
That just isn't true. The impact of comhost on the muxer was discussed extensively.
I agree that coverage is an important consideration. I am hoping the start of the native testing here will allow us to do just that. @vitek-karas also mentioned you had started work on some kind of native testing. Is your work applicable here?
There was a problem hiding this comment.
That just isn't true
Offline
Is your work applicable here?
The unit and E2E tests are checked in. In my opinion they were sufficient to test the SemVer 2.0.0 work.
There was a problem hiding this comment.
I agree we need to be extra careful. That said we should not shy away from refactorings and other code changes which will improve the maintainability of the code base.
This particular change - I'm perfectly fine with it. The dead code concern is valid, but I don't think it's important enough to pollute the code with additional ifdefs and such. Yes - it will make dotnet.exe slightly larger, but the difference should be trivial - dotnet.exe size is not a big concern as long as it's reasonable. It will also make other hosts slightly larger, and in this case it matters a bit more - we should keep these hosts as small as possible. But the code we're adding is very small.
Re testing: We absolutely need to add more coverage for hosts - in general, but dotnet.exe and hostfxr specifically.
The tests @elinor-fung added for this specific feature are OKish. We will need to add a much larger coverage around the "finding hostfxr" algorithms. But it might be better to do that once we have agreement on 3.0 behavior - see dotnet/designs#58.
As for backward compatibility - we are testing this end-to-end as well. SDK has several tests targeting down-level apps. There's also AppCompat testing which is adding coverage for down-level scenarios.
There was a problem hiding this comment.
Since it turned out we didn't need the muxer behaviour in nethost after all, I went back to the preprocessor conditions.
| bool fxr_resolver::try_get_path(const host_mode_t mode, const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path) | ||
| { | ||
| pal::string_t fxr_dir; | ||
| #if FEATURE_APPHOST || FEATURE_LIBHOST |
There was a problem hiding this comment.
This isn't an exported function. There is no breaking change here.
| bool fxr_resolver::try_get_path(const host_mode_t mode, const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path) | ||
| { | ||
| pal::string_t fxr_dir; | ||
| #if FEATURE_APPHOST || FEATURE_LIBHOST |
There was a problem hiding this comment.
I agree we need to be extra careful. That said we should not shy away from refactorings and other code changes which will improve the maintainability of the code base.
This particular change - I'm perfectly fine with it. The dead code concern is valid, but I don't think it's important enough to pollute the code with additional ifdefs and such. Yes - it will make dotnet.exe slightly larger, but the difference should be trivial - dotnet.exe size is not a big concern as long as it's reasonable. It will also make other hosts slightly larger, and in this case it matters a bit more - we should keep these hosts as small as possible. But the code we're adding is very small.
Re testing: We absolutely need to add more coverage for hosts - in general, but dotnet.exe and hostfxr specifically.
The tests @elinor-fung added for this specific feature are OKish. We will need to add a much larger coverage around the "finding hostfxr" algorithms. But it might be better to do that once we have agreement on 3.0 behavior - see dotnet/designs#58.
As for backward compatibility - we are testing this end-to-end as well. SDK has several tests targeting down-level apps. There's also AppCompat testing which is adding coverage for down-level scenarios.
| #define NETHOST_API __declspec(dllimport) | ||
| #endif | ||
|
|
||
| #define NETHOST_CALLTYPE __stdcall |
There was a problem hiding this comment.
This is different from all the other libraries (hostfxr/hostpolicy) which I think use _cdecl. @AaronRobinsonMSFT is the expert here.
I'm not against this - I think having __stdcall is better for interoperability (Which is the main goal here) - I just want to make sure that this is intentional and intentionally different.
There was a problem hiding this comment.
@vitek-karas Bah... Yes, you are right. The rest of the core-setup API is __cdecl. I agree that using __stdcall is more consistent with coreclr.dll, comhost, ijwhost, and the proposed winrthost exports. I will say this is annoying since all of hostpolicy and hostfxr are __cdecl. So much annoyance here. Thanks for pointing this out. I think we should keep with __stdcall since it aligns with all non "internal" core-setup scenarios better. I consider interacting directly with hostfxr and hostpolicy more "internal" or at the very least "advanced" as compared to COM, IJW, WinRT, and the native loading scenarios which users could experiment with.
There was a problem hiding this comment.
Agreed - and I like the consistency among the "public" hosts. hostpolicy is not going to be used externally (it's just too hard to use directly). hostfxr uses will be advanced and so some complexity is OK.
There was a problem hiding this comment.
@elinor-fung In a previous review @vitek-karas mentioned the need for documentation of these kinds of contracts. I think the fact that we are starting to coalesce around common user scenarios using __stdcall on x86 is an important contract that should be documented and officially defined. I have observed you reading through the documentation of core-setup, is there an existing doc that could contain these kind of details or is a new one needed?
There was a problem hiding this comment.
I'd say we need a new one. Maybe what gets created for #5481 would be a reasonable place (if it is a doc for all exposed APIs - I could also see doing a separate 'advanced' hostfxr/hostpolicy doc and 'public' *host doc)
| // | ||
| // Return value: | ||
| // 0 on success, otherwise failure | ||
| // |
There was a problem hiding this comment.
👍 - it's great that you wrote this as a header which we can give out to external users of the library.
| using char_t = char; | ||
| #endif | ||
|
|
||
| using nethost_get_hostfxr_path_result_fn = void(*)(const char_t * hostfxr_path); |
There was a problem hiding this comment.
I know a proposed this in the design doc, but now I'm a bit hesitant.
Using the callback makes it harder to use from some languages. It means the caller has to store the value in a global variable, which introduces issues.
Maybe we should switch to the caller supplied buffer. The downside is that if the buffer is two small and two calls are required, we pay the price of searching the disk twice...
Not sure - but I slightly prefer the caller supplied buffer as it makes the caller much more flexible. And if we add a comment to supply something like MAX_PATH buffer on the first call, it should almost never happen that we would need a second call.
There was a problem hiding this comment.
Using the callback makes it harder to use from some languages. It means the caller has to store the value in a global variable, which introduces issues.
I would agree with this sentiment.
There was a problem hiding this comment.
Switched to a buffer + size supplied by the caller.
| pal::string_t root_path; | ||
| if (assembly_path == nullptr) | ||
| { | ||
| mode = host_mode_t::muxer; |
There was a problem hiding this comment.
Sorry - this is my bad.
We can't use the muxer behavior here. The muxer behavior assumes that the dotnet.exe is called and thus it searches next to it to find hostfxr - and nowhere else. That's effectively useless for the nethost scenarios, because the nethost will typically run in the cotext of some application, which will not have the hostfxr next to it.
We will need to add some more tweaks. I think the proper behavior should be:
assembly_pathisnullptr: basically theapphostbehavior but without the "root_path", so don't look next to itself. But do useDOTNET_ROOTand do look in global locations.assembly_pathspecified: exactly likeapphostbehavior - this is already below.
There was a problem hiding this comment.
Updated to stop using the muxer behaviour and follow what you outlined above.
- Look at environment variable and global locations when called without an assembly path (app/libhost behaviour minus app-local search) - Switch back to preprocessor conditions for getting fxr path now that nethost doesn't need different behaviours - Update/add tests for changed behaviour
e659a61 to
206eed4
Compare
vitek-karas
left a comment
There was a problem hiding this comment.
Aside from the tracing setup it looks good!
| { | ||
| result.Should().Fail() | ||
| .And.ExitWith(1) | ||
| .And.HaveStdOutContaining($"{GetHostFxrPath} failed: 0x{CoreHostLibMissingFailure.ToString("x")}"); |
There was a problem hiding this comment.
You could additionally validate that the error written out is expected - through enabling tracing.
| { | ||
| if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||
| { | ||
| // We don't have a good way of hooking into how the product looks for global installations yet. |
There was a problem hiding this comment.
We will need something here eventually... 👍
| .And.HaveStdOutContaining($"hostfxr_path: {hostFxrPath}".ToLower()); | ||
| } | ||
|
|
||
| private class TestRegistryKeyOverride : IDisposable |
There was a problem hiding this comment.
Can you please expose this to all tests and refactor the one in SDK resolution to use the same class? It does exactly the same thing.
There was a problem hiding this comment.
Pulled out into RegisteredInstallKeyOverride class and switched other tests to it.
| // The full search for the hostfxr library is done on every call. To minimize the need | ||
| // to call this function multiple times, pass a large result_buffer (e.g. PATH_MAX). | ||
| // | ||
| extern "C" NETHOST_API int NETHOST_CALLTYPE nethost_get_hostfxr_path( |
There was a problem hiding this comment.
Is it expected that native hosters carry this dll with them rather than it being app local? Ex if I have a global module, and I wanted to find hostfxr, I would need to have nethost.dll sit side by side with my global module?
There was a problem hiding this comment.
Yes, the expectation is that a native host application will ship this library as part of the application itself.
There was a problem hiding this comment.
I think I need a bit of clarification. The expectation is that a published app will contain the nethost.dll, like:
published/
nethost.dll
myapp.dll
Also, is the nethost.dll opt-in or opt-out?
There was a problem hiding this comment.
The nethost.dll is to be part of the native host - so in the case of ASP.NET it will be part of the ASP.NET IIS module.
- .NET Core app doesn't need it, since the
apphostor muxer will find thehostfxrfor it and if it needs the path tohostfxrat runtime it can simply ask for already loaded module. - non .NET Core app which uses
hostfxrto host .NET Core (this is what we call "native host") will include it in itself.
Basically the ASP.NET IIS module has the "locate hostfxr" code copied into it today, with this it would just carry it as a separate dll.
We don't have a delivery vehicle yet, but we're thinking native (C/C++) NuGet package and probably just raw .zip with the library and header file for all other cases (Linux/macOS, non-C/C++ languages).
There was a problem hiding this comment.
Our IIS module today installs via an msi, so I'd imagine we should probably include it SxS with the module.
Once we get new bits from core-setup, I will start prototyping and see if all scenarios we used to support still work with nethost.dll.
There was a problem hiding this comment.
@jkotalik Just to clarify for me
include it SxS with the module
This means that you would package the nethost with your module and install it into the same folder on the target machine... right?
Re the compatibility - if that's not the case (that is you find a case where it works differently then your existing code), please let us know. It's using the same code as apphost and so if there's a problem in nethost it's likely there is a problem in the other "hosts" as well.
There was a problem hiding this comment.
This means that you would package the nethost with your module and install it into the same folder on the target machine... right?
Yes I believe that is the best course of action.
Re the compatibility - if that's not the case (that is you find a case where it works differently then your existing code), please let us know. It's using the same code as apphost and so if there's a problem in nethost it's likely there is a problem in the other "hosts" as well.
I'm less concerned about that than some of the backups we did. For example, if all else fails with finding dotnet, we search in C:\PF\dotnet as a backup. So we will see!
* Add nethost library and implement nethost_get_hostfxr_path * Add basic tests for nethost_get_hostfxr_path Commit migrated from dotnet/core-setup@6001915