microsoft / workbooks

Workbooks is an interactive programming environment that’s perfect for experimentation, learning, and documentation: an educational tool for learning the myriad of .NET platforms, APIs, and libraries.
MIT License
466 stars 79 forks source link

Proposal: Support Configurable Native Dependencies #204

Open mattleibow opened 6 years ago

mattleibow commented 6 years ago

I was thinking that we should support resolving native dependencies by configuration rather than trying to just guess. We can fall back to a few guesses if there is no configuration.

Right now we have a few hacks here: https://github.com/Microsoft/workbooks/blob/27720f75a7ab121316fcf8059fb14e1845de3d53/Clients/Xamarin.Interactive.Client/Reflection/NativeDependencyResolver.cs

Idea 1

My original hope was to use the <nuget-root>/runtimes/<platform>/native/<library-name> convention, but then I noticed that there is no official spec for that for iOS or tvOS. There is a file here that appears to have things like android, osx, win: https://github.com/dotnet/corefx/blob/6bfbf57294539d354f393267e1004c3045b906c7/pkg/Microsoft.NETCore.Platforms/runtime.compatibility.json

We can still just go ahead and make a few folders up and say if it is for iOS, then assume there may be an ios folder. However, this probably needs to be done with the support of the NuGet team so that we get it right. we don't want to do all this and then they go ahead and set the name to be xamarinios instead.

Idea 2

Another idea for configuration was to have a file in the xamarin.interactive folder that specifies locations and types of native files. This will go next to the assembly. An example configuration file might be: config.xml

<configuration>
  <nativeDependencies>
    <native platform="osx">/runtimes/osx/native/libDependency.dylib</native>
    <native platform="ios">/runtimes/ios/native/libDependency.framework</native>
    <native platform="win" arch="x86">/runtimes/win-x86/native/libDependency.dll</native>
    <native platform="win" arch="x64">/runtimes/win-x64/native/libDependency.dll</native>
    <native platform="android" arch="arm64">/runtimes/android-arm64/native/libDependency.so</native>
  <native>
</nativeDependencies>

Conclusion

This also should be developed alongside the other Xamarin platform teams. Right now the only way to include a native library is to either embed it into the assembly, or to create a targets file. If we can do what .net core does and use the runtimes folder, then we shouldn't need this config file. But, since they can't move as fast, we can still discuss with them what folder names they will use so that when they support the runtimes, then we should just be able to drop this config and rely on the platform.

So, as a final conclusion, It would be really great to use the runtimes folder, but the tooling doesn't support that just yet. If we can agree with the platform teams and the NuGet teams, then we could just start implementing the conventions right now.

The best thing is the conventions, and will not require any real code changes, because instead of:

if (path.Name == "SkiaSharp.dll")
    nativeDependencies.AddRange (GetSkiaSharpDependencies (path));

we will just do this:

nativeDependencies.AddRange (GetNativeDependencies (path));

this is because the work is half done already, just that we check for a specific libSkiaSharp.dll instead of just loading all the dlls that exist.

zwaap commented 6 years ago

Right now the only way to include a native library is to either embed it into the assembly, or to create a targets file

@mattleibow sorry to veer slightly off topic, but is there any guidance out there regarding this? I work on building a 3rd party API, and our architecture is a .NET managed wrapper on top of native dependencies. On the Android side, we actually do embed the native .so libs in the .dll, while on iOS, we link the native frameworks via a .targets file that gets pulled in through our NuGet package. In both cases, the native dependencies are not resolved while running the workbooks agent, so any p/invoke calls (which is basically everything in our API) fail.

I've been diving into the use of workbooks with our product the last few days and was angling toward logging an issue for this, when I saw that you've logged something that, if I understand it right, basically proposes adding what we need for our API to "just work" with workbooks. But in the meantime, is there some way to compel native dependencies to be resolved? Perhaps via an integration assembly? Your comment seems to imply that there is a path, but I've not been able to find any further information nor hack my way into a solution.

mattleibow commented 6 years ago

There is no way right now to do this automatically. For SkiaSharp we are using some hacks: https://github.com/Microsoft/workbooks/blob/27720f75a7ab121316fcf8059fb14e1845de3d53/Clients/Xamarin.Interactive.Client/Reflection/NativeDependencyResolver.cs

But, there is hope! Before these hacks, I was able to do this: UsingNative.workbook.zip

Basically, Workbooks allows for you to load a script. So what I did was to find the managed assembly, and then copy the native files next to it. This was actually used for a few other workbooks until the hacks arrived.

zwaap commented 6 years ago

@mattleibow much thanks for that. Do you happen to know whether co-locating the native dependencies with the managed assembly works for iOS and Android as well? Unless I'm misunderstanding, the script and NativeDependencyResolver hacks indicate that SkiaSharp is just for Windows and MacOS.

I'm guessing that the co-location works on desktops because the .NET/Mono runtimes will themselves look for native dependencies next to the managed assembly, which in the desktop case is accessible directly from the NuGet package's location. But that wouldn't work in the iOS/Android case unless workbooks is kind enough to actually deploy the native dependencies in addition to the managed assembly. That could work for Android, but I don't think that would for iOS unless workbooks could somehow link the native frameworks to the workbooks iOS app and re-deploy, since everything on iOS needs to be statically linked up front, no?

If it's an unknown on these platforms, I guess I'll just have to give it a go and see what happens! Failing that, I do see now that this might work on iOS if the native dependencies are embedded into the managed assembly (see here) instead of laid down separately, so that might be another option to get it working for now.

Sorry for the long post - will happily take this conversation to another channel if you can point me to one better suited.

sandyarmstrong commented 6 years ago

Unless I'm misunderstanding, the script and NativeDependencyResolver hacks indicate that SkiaSharp is just for Windows and MacOS.

Nope, it's for iOS and Android as well.

I'm guessing that the co-location works on desktops because the .NET/Mono runtimes will themselves look for native dependencies next to the managed assembly, which in the desktop case is accessible directly from the NuGet package's location. But that wouldn't work in the iOS/Android case unless workbooks is kind enough to actually deploy the native dependencies in addition to the managed assembly.

Workbooks does deploy native deps, but the topic at hand is: how do we identify those native deps? We have hard-coded support for a few packages, but it's hard to generalize.

sandyarmstrong commented 6 years ago

See https://github.com/Microsoft/workbooks/blob/1.4-release/Clients/Xamarin.Interactive.Client/Reflection/NativeDependencyResolver.cs

zwaap commented 6 years ago

Thanks again for the follow-up. Unfortunately, I haven't been able to get this to work for iOS or Android. I placed the native dependencies at the same level as the managed assembly as well as in subfolders along-side the assembly (e.g. under lib\x86 on Android and in the <FrameworkName>.framework folder on iOS). In all cases, the native dependencies did not appear to be deployed to the target environment. If there is indeed a trick to getting workbooks to pick these up, I'm missing it.

I've looked at the NativeDependencyResolver implementation a number of times now - even linked to a section of it in my previous post - and that doesn't appear to shed any light on the question of deployment. It looks to me like that deals with discovery of the native dependencies at code-inspection/compilation time. And as far as that goes, it looks like it only handles two basic cases: (1) native frameworks embedded in iOS assemblies (here) and (2) the resolution of dependencies for three particular assemblies (here). Maybe I'm missing something, but I don't see anything that suggests discovery of native binaries placed next to the managed assembly. Perhaps this worked at an earlier version but was removed at some point?

sandyarmstrong commented 6 years ago

I guess I was unclear before.

Everything resolved by NativeDependencyResolver does get deployed. But you are correct: short of embedded iOS frameworks, there is currently no way for the resolver to identify any native deps aside from the hard-coded ones.

So you cannot currently expect Workbooks to pick up your package's native deps.

That is part of the reason that this Github issue exists.

FWIW, lack of generalized support for native deps is mentioned in our release notes: https://developer.xamarin.com/releases/interactive/interactive-1.4/#Known_Issues.

sandyarmstrong commented 6 years ago

If you want to see how native deps are deployed and loaded, search the code base for ExternalDependencies.