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

API for performing framework checks at runtime #20752

Open
terrajobst opened this issue Mar 23, 2017 · 29 comments
Open

API for performing framework checks at runtime #20752

terrajobst opened this issue Mar 23, 2017 · 29 comments
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Runtime
Milestone

Comments

@terrajobst
Copy link
Member

Provide an API that allows developers to check whether their code runs on a given .NET platform.

// Assembly: System.Runtime.InteropServices.RuntimeInformation

namespace System.Runtime.InteropServices
{
    public partial class RuntimeInformation
    {
        public static bool FrameworkIsAtLeast(string targetFramework);
    }
}

Usage

if (RuntimeInformation.FrameworkIsAtLeast("netcoreapp10"))
{
    DoStuffThatOnlyWorksInNetCore();
}
else if (RuntimeInformation.FrameworkIsAtLeast("net462"))
{
    DoStuffThatOnlyWorksInNetFramework();
}
else
{
    throw new PlatformNotSupportedException();
}

Requirements

  1. Provide a robust API that allows developers to perform framework checks
    • In other words must not require parsing or comparisons by the developer.
  2. Use terms the developer already uses when they do compile-time specialization using cross-targeting ("bait & switch")
    • The strings passed to the API should be identical to the string used in project files and NuGet packages
    • In other words, we will not use the canonical TFM representation as it's
      wordy and generally not used by developers (e.g. .NETFramework, Version=4.6.1)
  3. Design should allow for an API that we can put in-box
    • Our goal is to avoid having to ask library authors to depend on an extra NuGet package that they have to deploy just for light-up
    • Means we cannot have APIs that imply each time a new .NET platform (or version ships) all platforms have to update their implementation
    • In other words, an implementer should only have to correctly answer questions affecting itself, i.e. it shouldn't have to know how other platforms behave or what names they use.
  4. API should work on .NET Framework 4.6.1
    • That's the minimum platform we support with .NET Standard 2.0, so it will be a common target and hence needs to work
  5. API should be addable to .NET Standard 2.0
    • This makes (3) much nicer today
    • Effectively only means we cannot add members to a type that already exists in .NET Framework
  6. The API must answer what the application runs on (as opposed to what the application is built for).
    • This is different from AppContext.TargetFrameworkName

Decisions

  • Originally we considered having properties with well-known names (like Net461). Having those properties violates requirement (3).
  • We considered adding the API on a more-well-known type, such as AppContext or Environment. We believe this API to be quite specialized and should only be used as the last resort. As such, we don't want to overly emphasize its existence. Also, it would violate requirement (4) and (by extension) requirement (5).
  • We considered using FrameworkName instead of string but this would require FrameworkName to parse NuGet-style TFMs into their canonical representations, i.e. FrameworkName.Parse("net45)" needs to be parsed into FrameworkName { Identifier=".NETFramework", Version=new Version(4, 5, 0, 0) }. While that would be nice, it violates requirement (3).

Context

Today, we have the following APIs that return version numbers:

namespace System
{
    public static partial class AppContext
    {
        public static string TargetFrameworkName { get; }
    }
    public sealed partial class Environment
    {
        public static OperatingSystem OSVersion { get; }
        public static Version Version { get; }
    }
}
namespace System.Runtime.InteropServices
{
    public static partial class RuntimeInformation
    {
        public static string FrameworkDescription { get; }
        public static Architecture OSArchitecture { get; }
        public static string OSDescription { get; }
        public static Architecture ProcessArchitecture { get; }
        public static bool IsOSPlatform(OSPlatform osPlatform);
    }
}

Neither of them deal with concepts that the developer is familiar with when building the application, specifically the target framework names and version numbers (e.g. net45 and netcoreapp11).

Furthermore, their behavior is less than ideal. Here is the output from a .NET Core console app:

Console.WriteLine(AppContext.TargetFrameworkName);          // <null>
Console.WriteLine(RuntimeInformation.FrameworkDescription); // .NET Core 4.6.00001.0

Our original design goal of RuntimeInformation was to provide developers with the diagnostic means to discover which .NET platform and operating system they are running on while not exposing version checking because it's a known area for creating fragile code. Unfortunately, the resulting API surface is now entirely insufficient for runtime light-up.

For better or worse, we've have to accept that developers will have to write code that allows them to tweak the behavior at runtime. We need to expose APIs that make it easy for them to express framework checks.

@weshaggard
Copy link
Member

We have AppContext.TargetFrameworkName which is supposed to be this. It is just a matter of getting it setup correctly on all the platforms.

@akoeplinger
Copy link
Member

The RuntimeInformation assembly/package is already out-of-band today [...]

It's not out of band on Mono/Xamarin.

@terrajobst
Copy link
Member Author

It's not out of band on Mono/Xamarin.

That's very unfortunate.

@terrajobst
Copy link
Member Author

terrajobst commented Mar 23, 2017

We have AppContext.TargetFrameworkName

Yes, but it ships in mscorlib in .NET Framework. Thus, we cannot add new properties representing new frameworks out-of-band, which defeats the purpose for runtime light-up. Also, it returns string. Writing version checks is now very inconvenient and thus error prone, like this:

var currentFramework = FrameworkName.Parse(AppContext.TargetFrameworkName);
var desiredFramework = new FrameworkName(".NETFramework", new Version(4, 6, 2, 0));

if (currentFramework.Identifier == desiredFramework.Identifier &&
    currentFramework.Profile == desiredFramework.Profile &&
    currentFramework.Version >= desiredFramework.Version)
{
    CallApiThatOnlyWorksInNetFramework();
}

We know version checks are hard and people never get them right. If we want to stand a chance, it needs to be a one liner like this:

if (TargetFramework.CurrentIsNet462OrHigher())
{
    CallApiThatOnlyWorksInNetFramework();
}

@terrajobst
Copy link
Member Author

I believe we have enough information to discuss it next time we look over the backlog.

@svick
Copy link
Contributor

svick commented Mar 24, 2017

Are all those CurrentIs*OrHigher methods necessary? What about having a method like CurrentIsSpecifiedOrHigher() (possibly with a better name), so that instead of:

if (TargetFramework.CurrentIsNet462OrHigher())

you would write:

if (TargetFramework.CurrentIsSpecifiedOrHigher(TargetFramework.Net462))

@akoeplinger
Copy link
Member

Would we return true for the NetXX cases in Mono? I guess not since that'd probably defeat the purpose. Do we need IsMonoXX then? What about all the Xamarin frameworks?

It doesn't sound very convincing to me.

@terrajobst terrajobst changed the title API for retrieving the target framework of the current runtime API for performing framework checks at runtime Apr 18, 2017
@terrajobst
Copy link
Member Author

terrajobst commented Apr 18, 2017

Would we return true for the NetXX cases in Mono? I guess not since that'd probably defeat the purpose. Do we need IsMonoXX then? What about all the Xamarin frameworks?

I think we want symmetry with NuGet packaging, i.e. you should answer the question considering people perform runtime light-up. Mono, while compatible with .NET Framework, isn't .NET Framework. So I would expect it to return false. Folks that want to handle .NET Framework & Mono the same way can easily do so, but they cannot easily do the reverse if they have to special case one over the other -- which I would expect to be the common case.

@weshaggard
Copy link
Member

@terrajobst are you planning to update the proposal here?

@kumpera
Copy link
Contributor

kumpera commented Apr 18, 2017

Hi Immo,

Who's the target of this API?
What sort of things is this meant to allow the user to dynamically select upon?

My concern is that this API makes it harder to query for capabilities and available surface by only allowing querying for the underlying implementation.

A few examples of things that are hard to do with this API:

  • Are appdomains available?
  • Can it JIT?
  • Is API XXX available?

All of the above would require checking for multiple implementations under your proposal to cover all our implementations.

@marek-safar
Copy link
Contributor

I agree that the API should check if a feature is available/supported and not if string matches all possible supported frameworks.

I think it'd make more sense to unify this with https://github.com/dotnet/corefx/issues/17116.

@terrajobst
Copy link
Member Author

terrajobst commented Apr 19, 2017

@kumpera @marek-safar

The target audience of this feature is library developers who need to tweak runtime behavior for specific platforms. This could be targeted light-up or working around specific issues. This API isn't meaningful for app developers as they know what they are targeting.

Our general approach is what you suggest:

  • Feature detection. Optional features, such as the registry, will provide an API you can query for, e.g. Registry.IsAvailable. Of course, this only works for cases where we can add new APIs, which isn't always possible without losing support for specific platform versions.

  • Runtime semantics. Add helper method to describe features supported by the runtime  #20623 is about allowing the compiler to statically discover which runtime features/type system capabilities it can rely on for emitting that will work. It also provides a runtime check so that this code can be used when emitting code on the fly, either by reflection or when generating & compiling code at runtime using Roslyn. I don't think unifying this with this issue is a good idea as these are very different scenarios, for a different audience.

  • Working around implementation level issues. Sometimes you need to work around specific behavior that is displayed by specific platform versions.

  • Version APIs are problematic. Believe me, we know very well how version checks are fragile. We've tried several times to remove version checks from our API surface. Be it in .NET Core or in Windows. But the reality is that some problems are just too hard to do reliably -- regardless of the approach. Giving developers the ability to perform version checks as the ultimate escape hatch is a sensible compromise. There is a reason why we don't want to make this API super discoverable and also turn the logic inside out, i.e. let the developer pass in the value they care about so we can do the version checking for them.

@marek-safar
Copy link
Contributor

I don't see why distinguish between Feature detection and Runtime semantic. The Feature detection as you describe could end up in situation where every class has IsAvailable method and in worse case even more than one and as you wrote this would work for new types only.

Further, is reflection emit limitation runtime semantic feature or feature detection? Similarly is lack of sockets support feature detection or runtime semantic? I think we are making things more complicated that they have to be.

I think your proposal for implementation level issue can work for closed and slowly moving platforms like .NET. For open source code released often you can end up with more versions than you can imagine (ignoring that someone can build/fork his own version) and that's only single platform. Even worse if you have multiple platforms/implementation you'd have to check for all of them (automatically excluding any new one) ending up with version check for .NET, CoreFX, Mono, Xamarin, Unity and so on and nobody is going to do that.

@terrajobst
Copy link
Member Author

I don't see why distinguish between Feature detection and Runtime semantic.

How would a centralized API be able to answer questions for all libraries?

Further, is reflection emit limitation runtime semantic feature or feature detection? Similarly is lack of sockets support feature detection or runtime semantic?

If the feature has an API, its much simpler if we provide a capability API. Whether a specific type system feature is available (like default implementations of interfaces) isn't relevant for 99.9% of our customers.

@kumpera
Copy link
Contributor

kumpera commented Apr 21, 2017

Immo,

My concern with this API is that it will be unusable for library authors targeting Xamarin. WE ship what's effectively 6 platforms (desktop, iOS, tvOS, watchOS, Android, 1 console) with more coming this year. Each one come with their own particularities and customization.

Then you add up our large embeders such as Unity that customize both runtime and BCL.

Finally, take into account the volume of releases we do - right now we're working on our 3rd release of the year.

As much as this API is great for CLR & CoreCLR that combined add 2-3 versions a year, it would be a nightmare for us that produce an order of magnitude more.

I understand the reasoning for adding this feature, but its adoption would be objectively detrimental to mono/Xamarin as library authors would drown in the complexity of version checking against us.

Maybe there are some very compelling use cases missing from this PR that would make its case.

@terrajobst
Copy link
Member Author

terrajobst commented Jun 22, 2017

It seems we should have a meeting an discuss this in more detail. I feel like we're slightly talking passed each other here. I'll setup something.

@ViktorHofer
Copy link
Member

Any update to share? Is the proposed API stall?

@ghost
Copy link

ghost commented Jan 11, 2018

I would like to add an example use case.

Security Use Case

Applications published as self contained products must be able to exhibit to administrative users the version they are compiled against and bundled with for security reasons (eg. CVE-2018-0786). This is important in larger enterprises where security administrative staff may differ from engineering staff. A web application may display such information on a control panel version page, a linux daemon may show it in it's help output.

dotnet-bot referenced this issue in dotnet/corefx Apr 10, 2018
…nce (#17452)

* Fix MemoryManager ctor, add unit and perf tests, and use internal span ctor.

* Address PR feedback (remove use of Unsafe.As and Dangerous Span Ctor)

Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
ahsonkhan referenced this issue in ahsonkhan/corefx Apr 10, 2018
…nce (#17452)

* Fix MemoryManager ctor, add unit and perf tests, and use internal span ctor.

* Address PR feedback (remove use of Unsafe.As and Dangerous Span Ctor)

Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
ahsonkhan referenced this issue in dotnet/corefx Apr 10, 2018
…nce (#28880)

* Fix MemoryManager ctor, add unit and perf tests, and improve performance.

* Remove Dangerous Span Ctor

* Fix sort order in csproj and rename Perf.MemorySlice.cs to Perf.Memory.Slice

* Fix MemoryManager ctor and use internal span ctor to improve performance (#17452)

* Fix MemoryManager ctor, add unit and perf tests, and use internal span ctor.

* Address PR feedback (remove use of Unsafe.As and Dangerous Span Ctor)

Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
dotnet-bot referenced this issue in dotnet/corefx Apr 10, 2018
…nce (#17452)

* Fix MemoryManager ctor, add unit and perf tests, and use internal span ctor.

* Address PR feedback (remove use of Unsafe.As and Dangerous Span Ctor)

Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
@jazzdelightsme
Copy link
Contributor

Here's my use case:

I am working on a PowerShell script that consumes the Active Directory Authentication Library NuGet package (ADAL.NET) to do auth with the VSTS REST API. The latest (v4 preview) package contains binaries for various platforms: net45, netstandard1.1, netstandard1.3, uap10.0, etc.

PowerShell v5.1 (“Windows PowerShell”) runs only on Windows, on the full .NET Framework. PowerShell v6 (“PowerShell Core”) is cross-platform, and runs on .NET Core.

I had hoped that I could just use the netstandard1.3 version of the ADAL.NET library, and that it would “light up” when run on Windows PowerShell 5.1 on the full framework, but that’s not how they wrote it. So in my script, I need to detect: am I running on full .NET? If so, load $PSScriptRoot\adal\net45\adal.dll. Or am I running on .NET Core? In which case, load $PSScriptRoot\adal\netstandard1.3\adal.dll.

In my particular situation, I don’t currently need a version check, because PSv6 runs on a very recent .NET Core, and the highest netstandard version of the ADAL.NET library is netstandard1.3 But in the future, the situation could easily become that I need both a platform “flavor” check, and a version check. (Future me: should I load the netstandard3.0 binary? Or am I running on an ancient version of PowerShell, and I have to drop back to the netstandard2.0 binary?)

So in terms of this discussion, my PowerShell script functions as a library that doesn’t know what sort of host it has been loaded into, and needs to find out so that it can load the right versions of its own dependencies.

I should also note that there is no "setup" to speak of where I could decide which version of the binary to install (therefore the check needs to happen at runtime), and additionally, even if there were a setup, I wouldn't want to or couldn't decide which version of ADAL.NET to install, because my script could be run from multiple different versions of PowerShell. (I load the same script, from the same location on my disk, from both Windows PowerShell, PowerShell Core on Windows, and PowerShell Core on Linux via WSL. It's also common to run scripts directly off of a network share.)

@AceHack
Copy link

AceHack commented Jan 15, 2019

An update?

@terrajobst
Copy link
Member Author

terrajobst commented Feb 25, 2019

/cc @richlander

I've marked this for 3.0 (and removed the 'needs-work' marker so it gets back on our radar for review). We should discuss this again.

@richlander
Copy link
Member

I would like to see how this proposal related to AppContext and any .NET Core compat plans around quirks.

@terrajobst
Copy link
Member Author

I've bumped this back to needs more work because I've recently done some thinking that needs more closure.

@stephentoub
Copy link
Member

@terrajobst, I'm assuming this can be bumped out of 3.0?

@karelz
Copy link
Member

karelz commented Jun 3, 2019

@bartonjs @joperezr as area owners - do you agree? (I do)

@bartonjs
Copy link
Member

bartonjs commented Jun 3, 2019

By the power vested in me by an at-mention and the permissions model, I've moved the issue to Future. If anyone was prepared to do late-feature justification paperwork they can always move it back.

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 5.0 milestone Jan 31, 2020
@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 23, 2020
@joperezr joperezr modified the milestones: 5.0.0, Future Jul 6, 2020
@joperezr joperezr removed the untriaged New issue has not been triaged by the area owner label Jul 6, 2020
@webJose
Copy link

webJose commented Dec 27, 2020

Hello, newcomer here interested in the topic. I would like to add here for your discussion the current issue that brought me here:

I want to collect the runtime information for easier support. Nowadays we write frameworks for .Net Standard, but in the end, for a specific case that requires support, it might become meaningful which runtime ended up running the library code. So a simple way to obtain the runtime engine details would be amazing. I read Environment.Version returns the version of the executing runtime. Now if we could only know if it is Mono, Xamarin, .NetFramework, .NetCore, FutureSuperRuntime, etc.

@svick
Copy link
Contributor

svick commented Dec 27, 2020

@webJose I think you should be able to use RuntimeInformation.FrameworkDescription for that.

@webJose
Copy link

webJose commented Dec 28, 2020

@svick Thanks for the tip. I shall try that one out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Runtime
Projects
None yet
Development

No branches or pull requests