Closed ericsink closed 8 years ago
The novel I have written above is long and boring. Its characters are shallow and its plot is weak. I can't imagine anybody will enjoy reading this.
That said, I am looking for comments on it.
I would specifically like feedback from @borrrden or @zgramana, because your use case is directly related to the subject at hand.
And I would value feedback from @onovotny, because you have much knowledge about NuGet 3.x.
And, even though I know this will especially tax the patience of @praeclarum, I would value your feedback because so many users of SQLitePCL.raw actually get there through SQLite-net-PCL.
And @paulcbetts, because Akavache uses SQLitePCL.raw as well, and because you know lots of stuff, your comments would be welcomed.
And @bordoley, because you have made lots of contributions to SQLitePCL.raw over time, I invite your remarks on this.
I'll probably think of others I want to nag, but that'll do for now.
@ericsink In the spirit of fostering a good review/dialog, can we get this on a OneDrive word doc/onenote or shared google doc? It'd probably be easier to review and discuss/notate inline for this.
@onovotny Probably. ;-)
Lemme look into that. I don't regularly use any of the stuff you mentioned, but I suspect they won't be difficult.
All right, let's see if I did this right.
https://docs.google.com/document/d/1wdGctdH_JiG3O15a-SFhEfTQo9iYQt5TYXJHcLjNUjM/edit?usp=sharing
I think I've configured this to allow comments by anyone. For anybody who prefers the shared Google document as a way of providing feedback, feel free.
If anybody wants the ability to not just add comments but also edit the document there, let me know and I'll figure out how to add you.
And for anybody who prefers to just comment here, that is welcomed as well.
I only skimmed this, but one minor correction - Xamarin.iOS Classic does use MSBuild now. It was optional in Cycle 5, and is always used in Cycle 6.
Also, for iOS Unified on iOS 8.0+ you could consider using an embedded framework?
@mhutch What would the benefit of an embedded framework be? I'm not up-to-speed on the tradeoffs there.
I put a bunch of thoughts/comments in the doc.
@ericsink you could ship a custom libsqlite build as as a shared library on iOS
Ah. Way cool. I didn't know that iOS 8 allowed a way to use dynamic libs. Thanks.
The key question in all of this is: In SQLitePCL.raw.dll, what name is provided to DllImport (pinvoke)?
None! All problems in computer science can be solved with another layer of indirection!
IE, don't put the DllImports in SQLitePCL.raw.dll. Put them in a (bait and switch) managed library that goes in the package for each native version of SQLite, and includes only the DllImports. Instead of a "shim" package with a native z_sqlite library that redirects calls to the version included in the OS, you can simply have another "native" SQLite package that just has the DllImports pointing at the version in the OS and no actual native library.
I think this is a bit of an improvement on what you describe, but I think I didn't follow all of it.
This still leaves the issue that you would like to have a default version of SQLite (that would be used for example if you install Akavache), but application authors should be able to override that default. It might be nice if NuGet could support this type of default/replaceable dependency. (OTOH it might just end up adding way too much complexity to NuGet.) If you can't find a way to detect at runtime which version of SQLite to use, then I think the next best option is to let the app configure via code whether the default or one of the custom versions should be used. IE by default SQLitePCL.raw.dll would end up calling into a PInvoke into "sqlite", for example, but if a flag was set then it would use a "z_Sqlite" PInvoke instead, and you would need to reference one of the native NuGet packages that provided the native library and corresponding PInvoke.
The idea from @dsplaisted is thought-provoking and deserves exploration. But my initial worry is: The indirection added by the DllImport-only package is still a NuGet-based operation, and that flavor of indirection still only supports the ability to choose implementations based on platform-like factors, not config options. (Correct me if I am misunderstanding something.)
For the moment, as a followup to comments from @onovotny over in the shared Google doc, I am making another attempt to find a way to move the choice of the DllImport library to a runtime decision.
The indirection added by the DllImport-only package is still a NuGet-based operation
Consider the following option:
There is an SQLitePCL.raw NuGet package. It does not contain any of the DllImports, or any package dependencies on packages that do. It is compiled against a "bait"/reference assembly version of a library that provides the DllImports.
There is an SQLite-default package. It contains the "switch" / implementation assembly version of the DllImports library, as well as when necessary the actual implementation of SQLite. It can do this on a per-platform basis based on the factors that NuGet supports.
For non-default config options, there are corresponding NuGet packages. For example, SQLite-Crypto and SQLite-Latest. These would also each contain the implementation of the DllImports, as well as the SQLite implementation with the corresponding options. They would also use NuGet to select the right version of SQLite and the DllImports for the OS/bitness/etc selected.
So developers would need to choose which "config" options of SQLite to get by choosing which implementation package of SQLite they installed. NuGet would automatically pick the right version for the OS/platform.
The main drawback of this design is that you wouldn't automatically get a default implementation of SQLite. So you install Akavache, and run your app, and get a TypeNotFoundException or something because you didn't reference an SQLite implementation package and didn't get the DLL with the DllImports (as well as an SQLite implementation, if necessary).
To improve this experience, runtime detection would be ideal, so the SQLitePCL.raw NuGet package would work out of the box but if you installed one of the non-default config option implementation packages it would automatically use that instead. If runtime detection isn't possible, my suggestion was to have a config option in SQLitePCL.raw that could be set by the application code to tell it whether to look for a non-default SQLite implementation.
Also note that in this design if you want to reduce package size, you can split the SQLite implementation package for each custom config option into multiple packages, and use runtime.json (as @onovotny mentions in the doc) to select the right one for the current target.
"or any package dependencies on packages that do"
The phrase above is the issue that was getting me stuck.
Let me explain what I think you are saying:
The main assembly (SQLitePCL.raw.dll) now expects the static class containing the DllImports to be provided by another assembly, which for now I will call MyImports.dll. When I build SQLitePCL.raw.dll, I provide it with a reference copy of MyImports.dll. But when I package SQLitePCL.raw.dll into its NuGet package, I do not include MyImports.dll, nor do I include a NuGet dependency.
(Several different implementations of MyImports.dll would exist. Folks need to pick the one that has the features they want.)
I think this would mean that adding SQLitePCL.raw to a project will not even build until one of the various MyImports dlls is added as well.
Does this match your thoughts? Or were you thinking of something where SQLitePCL.raw.dll used reflection?
Your comments about "runtime detection would be ideal" seem suggestive of a more like a reflection-based solution. But for SQLitePCL.raw to work out of the box whenever a MyImports.dll is not present, that would mean that the main assembly actually does have its own set of DllImports.
Either way, I think you've suggested something interesting that was in my blind spot.
@ericsink https://github.com/ericsink you could ship a custom libsqlite build as as a shared library on iOS
Oh please don’t.
@praeclarum I'm curious for more detail about the feeling of dread you apparently have about shared libs on iOS.
That said, I wasn't leaning in that direction anyway. Just curious.
Nothing against shared libs - everything against popping in our own custom sqlite. The system one is tested and stable. It’s updated automatically. I guess I dread maintaining that custom build.
That said, there are pros: adding full text indexing requires a custom built sqlite, some of that crazy encryption stuff also requires it. So I guess there is some utility to be gained with a pluggable back end.
On Jan 13, 2016, at 10:33 AM, Eric Sink notifications@github.com wrote:
@praeclarum https://github.com/praeclarum I'm curious for more detail about the feeling of dread you apparently have about shared libs on iOS.
That said, I wasn't leaning in that direction anyway. Just curious.
— Reply to this email directly or view it on GitHub https://github.com/ericsink/SQLitePCL.raw/issues/66#issuecomment-171390251.
Ah. Well, FWIW, that ship has already sailed. :-)
SQLitePCL.raw already supports the ability to use either the OS-provided SQLite (by default) or a custom build. And yes, this is for the reasons you mentioned (full text indexing, crypto, etc).
It could be said that a major goal of this project is to provide that flexibility without letting it cause too much hassle. I will consider it a small point of success that you maintain a popular wrapper but did not even know of the support for custom builds, since that apparently means the flexibility has not caused you enough pain to notice. :-)
FWIW, I wasn't suggesting shipping sqlite as a shared lib, just pointing out that it was possible :)
I think this would mean that adding SQLitePCL.raw to a project will not even build until one of the various MyImports dlls is added as well.
Does this match your thoughts?
Yes, this is what I was describing in the first part. I thought it would be easier to understand if I first described a simpler system.
However, the simple system has the unfortunate property that it's not a very good developer experience. So I think in addition you would want some other way of handling a default SQLite implementation (eg runtime detection or configuration).
Over here we are also having lengthy discussions about this very topic! I've skimmed over what you've written but I'm a bit too tired at the moment to last through the whole thing. I'll give it another look soon!
OK, just an update:
I've made significant progress, and I think I know where this is going to end up. But let's say that all of the following is tentative until it's actually done.
The main SQLitePCL.raw package is very clean. It no longer contains native code. It pinvokes with the library name "sqlite3", which means it should work with the default SQLite3 library on each platform. If you don't need a custom SQLite build, that's all you need. Just add the SQLitePCL.raw package and it should just work.
There will be two optional mechanisms for using a custom SQLite build:
(1) Actually, the main SQLitePCL.raw package will first try to pinvoke from a different name at runtime. Right now that name is "esqlite3" (note the e). If it finds a dynamic library with that name, it will use that. If it does not, it just falls back to the default pinvoke of "sqlite3" (without the e). (This was done by embedding two copies of the DllImports.)
So, if you want to use a custom SQLite build on Windows for example, just build it as esqlite3.dll and put it in the right place.
(2) The other way to use a custom SQLite build is more explicit and involves calling a function. That function's parameter must be an object which implements the ISQLite3Provider interface defined in SQLitePCL.raw. (This allows for a separate assembly to have a copy of those DllImports.)
At this point it looks like method (1) above will work everywhere except iOS. It should work on iOS Unified by using an embedded framework, but so far it doesn't, probably because I'm doing something wrong. Anyway, method (2) above allows the use of iOS static linking, and works just fine.
I intend to provide SQLite3 and SQLCipher NuGet packages for most platforms, probably using method (2) for iOS and method (1) for everything else. FWIW, method (2) should work pretty much anywhere, although I will (probably) only provide that mechanism for platforms where it is needed (iOS), because it requires an extra step on the developer's part.
Finally, it is worth noting that the Windows Phone 8.0 and "Windows Phone 8.1 Silverlight" will be excluded from all of the above, since they do not support pinvoke. I plan to continue shipping basic support for those two platforms, but not to support the use of custom SQLite builds there.
Update:
I am waffling on one issue and would welcome feedback:
I am starting to prefer that the use of a custom SQLite build should require an explicit Init() call, rather than having the main package look for a DLL and try to load it automatically. It just feels cleaner to me to have an explicit dependency injection step.
If (like most users) you want to use the default SQLite library, just use the SQLitePCL.raw package, and it should Just Work.
If you want to use a custom SQLite library (like, for example, one with fulltext search or encryption), you also add the corresponding NuGet package (which will contain or depend on platform-specific unmanaged code) and call its Init() method (which will cause the package to register itself with SQLitePCL.raw as the implementation to be used).
Any objections?
I'm unfamiliar with what that Init() process would entail, but assuming it is possible this would be great. This would give us the ability to customize our SQLite build however we want and just put our own nuget package out there.
Having read into this a little more, I assume this means that you would signal SQLitePCL.raw to switch from the "regular" DLL name to the "custom" dll name manually. It might make sense to allow a custom ISQLite3Provider implementation to be injected as well in case the user wants to add some P/Invoke functions that are not in the default implementation. Perhaps an abstract class that implements the base functionality?
@borrrden Actually, the mechanism I've been implementing is much like the second thing you described, a way to inject a custom ISQLite3Provider implementation.
The class raw
has:
public static void SetProvider(ISQLite3Provider imp);
which can be used to inject whatever implementation you want.
The NuGet packages I plan to provide will contain something like:
public static void Init()
{
var imp = new SQLite3Provider_with_special_stuff();
raw.SetProvider(imp);
}
None of this is set in stone yet.
I don't have much skin in the game these days, but I'll chime in that I like the init approach. I'm almost incline to say that it should be required even when using the built in SQLite version, so that developers are aware that they are choosing to use the platform provided version versus an explicit version. This is especially relevant for platforms like Android where their is wide distribution of different operating system versions with different SQLite versions, which will of course have different capabilities. It might also be a good idea to update the default SQLiteProvider to explicitly throw an exception when using SQLite functions that are newer and not commonly supported across all prepackaged SQLite versions.
@bordoley I do find myself tempted by the idea of forcing the developer to be explicit even when they want the default SQLite build. But I doubt I will convince myself to be that cruel. :-)
FWIW, we're getting close to publishing a pre-release of 0.9.0.
The main SQLitePCL.raw package should just work for most people, and it has slimmed down to about 447 KB:
447536 SQLitePCL.raw.0.9.0-pre1.nupkg
49888 SQLitePCL.ugly.0.9.0-pre1.nupkg
For the additional packages, I'm almost settled on the terminology of a "plugin" instead of using the word "custom". They currently look like this:
4449915 SQLitePCL.plugin.sqlcipher.android.0.9.0-pre1.nupkg
8032667 SQLitePCL.plugin.sqlcipher.ios_classic.0.9.0-pre1.nupkg
8032681 SQLitePCL.plugin.sqlcipher.ios_unified.0.9.0-pre1.nupkg
1763512 SQLitePCL.plugin.sqlite3.android.0.9.0-pre1.nupkg
2992878 SQLitePCL.plugin.sqlite3.ios_classic.0.9.0-pre1.nupkg
2992881 SQLitePCL.plugin.sqlite3.ios_unified.0.9.0-pre1.nupkg
972642 SQLitePCL.plugin.sqlite3.net35.v110_xp.0.9.0-pre1.nupkg
972786 SQLitePCL.plugin.sqlite3.net40.v110_xp.0.9.0-pre1.nupkg
972787 SQLitePCL.plugin.sqlite3.net45.v110_xp.0.9.0-pre1.nupkg
1269105 SQLitePCL.plugin.sqlite3.uap10.0.v140.0.9.0-pre1.nupkg
1269088 SQLitePCL.plugin.sqlite3.win8.v110.0.9.0-pre1.nupkg
973032 SQLitePCL.plugin.sqlite3.win8.v110_xp.0.9.0-pre1.nupkg
973045 SQLitePCL.plugin.sqlite3.win81.v110_xp.0.9.0-pre1.nupkg
1265677 SQLitePCL.plugin.sqlite3.win81.v120.0.9.0-pre1.nupkg
To use a plugin, just add it to your app and call SQLite3Plugin.Init() from your equivalent of main().
For now, all the plugin packages still use targets files, not the NuGet 3.x stuff. That'll come.
For now, I have support for the same set of native code platforms I did before. More later.
I did add new native code packages built with the v110_xp toolset with the C runtime statically linked. Desktop apps can use these to avoid needing to add the C runtime lib.
I do not yet have a "switch" package. In other words, I don't have a package that figures out which plugin you need and then adds a dependency to that one.
I still need to update my sqlcipher builds to be more like the ones Couchbase Lite is using. Like using common crypto for the iOS version, for example.
I also want to do coordinate with @borrrden to make sure this new approach is going to work well with the plugin-ish approach you folks introduced with Couchbase Lite 1.2.
I'll push these packages to nugget as soon as I'm sure I am done changing the package IDs. If you hate the word "plugin", now is the time to say so. :-)
Excellent I've been waiting for this. Nice bonus with the statically linked C runtime (this has come up several times). Also since 1.2 I've further refactored out our managed code sources as well as our unmanaged one so that they truly work like plugins. Your method will be a welcome addition because it means I can avoid what I'm doing at the moment which is compiling SQLitePCL sources directly into my DLL to avoid msbuild fighting over which version of SQLitePCL gets copied (the official one or "mine").
I don't currently have the equivalent for the Init method in my system and this is both a pro and con, but if SQLitePCL has one it won't be a problem unless it absolutely has to be called from the top level project (I assume it would be ok for a library project inside the project to call it). It's a pro because it means that installing the package is all the user needs to do, but its a con because there will be either errors or arbitrary decisions if the user installs more than one of the same family of plugins. Also iOS cannot include plugins unless they are AOT compiled which makes things weird and necessitates the need for an init method or ghost refererences in the core assembly.
I don't hate the word plugin, though an alternative could be "support package"
@borrrden A coworker of mine is saying that I have too many packages. He wants me to have just three:
SQLitePCL.raw SQLitePCL.plugin.sqlite3 (which contains sqlite3 for all platforms) SQLitePCL.plugin.sqlcipher (which contains sqlcipher for all platforms)
I sympathize with the feeling of confusion people get when they bring up their IDE's nuget dialog, search for SQLitePCL, and see too many options. But more and more NuGet packages have this issue.
Anyway, do you have an opinion on this?
(I'll respond to your questions just above in a separate msg.)
@borrrden I have been envisioning that in your case, the Init() method would be called from inside your library.
And yes, one of the reasons I made the Init() method mandatory is to deal with the constant problems with iOS and its AOT compiler.
I would certainly prefer one package to contain all of the implementations for any given configuration. Otherwise Couchbase Lite will be taking deps on several different plugin packages. I'd definitely like to avoid the user having to add them (but that will still cause the next problem). The goal is to install Couchbase Lite and have everything work without additional packages being manually installed. To that end, I have chosen one plugin as the "default" (Couchbase.Lite.Storage.SystemSQLite) and made the Couchbase.Lite package have a dependency on it. In keeping with this goal, if everything were a separate package by platform we would have to add three additional packages as dependencies to make sure that we covered everything (one for each platform of a given implementation) and it would look very odd to a user if they had a "droid" package on their iOS project, etc.
So if you end up keeping them like this I imagine I would not make them nuget dependencies, but rather download them at compile time and package them all together in the Couchbase nuget package. Right now (in the master branch) the plugin packages look like this ->
SystemSQLite: dep on SQLitePCL.raw and SQLitePCL.ugly Couchbase.Lite.Storage.SystemSQLite.dll -> concrete implementation of our storage interface using the above two references (keeping them intact because they take care of the sqlite3.dll copying for me!)
SQLCipher: No deps (SQLitePCL.raw and ugly code is compiled into the below DLL) Couchbase.Lite.Storage.SQLCipher.dll -> concrete implementation of the storage interface using the internally compiled versions of raw and ugly that DllImport to sqlcipher A .targets file (made a lot of references to yours in the process of making them) that copies the correct sqlcipher native image, if applicable (Android and iOS both have these images compiled directly into the managed DLL)
ForestDB: No deps (cbforest-sharp.dll is compiled into the below DLL) Couchbase.Lite.Storage.ForestDB.dll -> concrete implementation of the storage interface using cbforest-sharp A .targets file that copies the correct CBForest-Interop native image, if applicable (Android and iOS both have these images compiled directly into the managed DLL)
Note: The concrete implementations get loaded at runtime via Type.GetType (indirectly calls Assembly.Load). The library is in control of which implementation will get used so even if the user decides to get cute and call Assembly.Load on another one, it won't have any effect other than taking up more memory.
This is what I would like to turn it into:
SystemSQLite dep on SQLitePCL.raw, SQLitePCL.ugly, SQLitePCL.plugin.sqlite3 Couchbase.Lite.Storage.SystemSQLite.dll -> concrete implementation of our storage interface that registers the SQLitePCL.plugin.sqlite3 plugin
SQLCipher dep on SQLitePCL.raw, SQLitePCL.ugly Couchbase.Lite.Storage.SQLCipher.dll -> concrete implementation of our storage interface, and SQLite provider implementation using our compiled version of sqlcipher A .targets file as described above
CustomSQLite (new package) dep on SQLitePCL.raw, SQLitePCL.ugly Couchbase.Lite.Storage.CustomSQLite.dll -> concrete implementation of our storage interface, and SQLite provider implementation using our compiled version of sqlite3 A .targets file similar to SQLCipher
ForestDB (No change)
P.S. Another angle would be to split the Couchbase.Lite.Storage packages into platforms, but this will infect everything up to the core package since I want the nuget dependency from Couchbase.Lite -> Couchbase.Lite.Storage.SystemSQLite. It would turn into Couchbase.Lite -> Couchbase.Lite.Storage.iOS.SystemSQLite, Couchbase.Lite.Storage.Android.SystemSQLite, Couchbase.Lite.Storage.Net45.SystemSQLite, Couchbase.Lite.Storage.Net35.SystemSQLite (I still split the last two because one needs the backport TPL and the other doesn't)
That's very helpful. Thanks. More later...
@onovotny A question:
I've had people gripe at me for assuming that "net45" (in NuGet) means Windows. They're using Mono on Linux and they want to consume net45 packages without getting Windows-specific stuff.
I've also had people tell me to ignore the people mentioned above, because net45 actually does imply Windows, and, well, cross-platform .NET stuff is what .NET Core is all about, so use "dotnet" and the new stuff in NuGet 3.x and look at .NET Standard.
What say you? Is it legit to assume that net45 (and net40, etc) always means Windows?
so use "dotnet" and the new stuff in NuGet 3.x and look at .NET Standard.
This will require support from Mono which seems to be a ways off, at least for IDE integration.
I don't think anyone can make the argument that net45 means Windows only unless they are using Nuget 3.3+, and I definitely don't want to think that people will start using net35 as Windows only since there is no dotnet equivalent.
Right now, Mono seems to choose the net45 stuff because it's essentially "desktop 4.5 compatible". There's really no way NuGet currently has to provide mono-specific assets. The closest it gets is for Xamarin iOS/Android/Mac platforms.
I think you should assume that net4/net45 is Windows but that this is "yet another thing that's borked on Mono" because there's no other way.
The only possible workaround would be to use msbuild targets to swap stuff out based on environment variables/IsUnix type of stuff.
In the end netstandard
is the way to go but it still won't currently differentiate mono from net45/net46. Clearly that's the goal. What you may have in NuGet v3 is the runtime id's -- of which mac/Linux are there. That's how the CoreFX libs provide Linux specific functionality. That's all NuGet v3 stuff though.
The idea is that you'd have a netstandard1.0
version for the MSIL parts and then potentially have many different runtimes
elements as that can pivot on Linux/OSX/x86/arm, etc. You shouldn't then care if it's .NET Core on Linux or Mono on Linux since they should be using the same assets.
@borrrden wrote: "I don't think anyone can make the argument that net45 means Windows only"
And yet, I assume that you want this proposed SQLitePCL.plugin.sqlite3 package to provide a Windows DLL for net45, right? :-)
Yes, and so I mean "Desktop .NET 4.5" (whether on Windows, OS X, or Linux, etc) as opposed to "Windows .NET 4.5". Your current solution doesn't copy Windows specific items when they are not needed. Or do you mean that people don't want them to even exist in the downloaded package at all?
EDIT I think my wording before might have been a little confusing so I added some emphasis
I don't think anyone can make the argument that net45 means Windows only unless...
I may have misunderstood the point you were trying to make though (I thought it was people saying that net45 should exclude platforms other than Windows)
"Your current solution doesn't copy Windows specific items when they are not needed."
I think I understand what you mean by the above. Basically, for net45 (and similar), by default (without the UseSQLiteFrom
msbuild property), SQLitePCL.raw (0.8.x) does not install any native code, and since it pinvokes from plain old "sqlite3", it probably Just Works for non-Windows platforms.
(For 0.9.0, the main SQLitePCL.raw package will work the same way.)
But the only purpose of these new "plugin" packages (however they get grouped) is to provide an implementation of the native code.
Although (as you have described) it would cause more hassle for the developer, the benefit of having the plugin packages all separate is that they produce a "layer" where I'm not making any assumptions that higher layers might regret. SQLitePCL.plugin.sqlite3.net45.v110_xp.0.9.0-pre1.nupkg is not a friendly name, but it's unambiguous. Once you know that "v110_xp" is a Visual Studio toolset, you know that this package is Windows-specific. Even if "net45" does not mean Windows, "net45.v110_xp" does.
So, we were talking earlier about a SQLitePCL.plugin.sqlite3.nupkg which contains multiple platforms. It (obviously) needs to know how to select the right one. In terms of the native code in this package, what do we want "net45" to mean?
From looking inside your cblite packages, it looks like the only things that matter for cblite right now are the Xamarin platforms and net35+net45. If you take a dep on the multi-platform SQLitePCL.plugin.sqlite3, when net45 gets selected for your package, I assume you want a Windows DLL (as opposed to any of the possible non-Windows connotations of net45).
That's what I mean above when I speak of "assuming that net45 means Windows".
Another example, but perhaps not currently applicable to cblite: For the multi-platform SQLitePCL.plugin.sqlite3, which build do I put in for win81? The one built against the most recent compatible C runtime library? Or the one built with v110_xp? The former will require the extra Visual C++ Runtime package to be added. The latter is built with the C runtime statically linked, so it requires no extra step. But are there cases where the end app developer needs to make sure they don't have two different copies of the C runtime in their app?
It's possible that I am being excessively pedantic. It wouldn't be the first time.
(Popping the stack and responding to your note that began with "I would certainly prefer one package ")
So for your SystemSQLite package, since 1.2 you've gone back to having nuget deps on SQLitePCL.raw? (Just clarifying.)
In your "This is what I would like to turn it into:" section, you mention that SystemSQLite would have a dep on the multi-platform SQLitePCL.plugin.sqlite3. Is that for Windows stuff? For "system SQLite", on iOS and Android you wouldn't need it, right?
For SQLCipher, I still think I might switch over to using your builds, which may or may not affect how you structure the deps for your SQLCipher package.
For your CustomSQLite package, dropping in the right C# code from my tree is straightforward. I've thought about trying to make that use-case even easier, but I haven't come up with anything obvious.
I've never actually used the nuget ability to vary the package deps per-platform:
https://docs.nuget.org/create/nuspec-reference#specifying-dependencies-in-version-2.0-and-above
But I've been planning to experiment with this to build a "switching" package, one which contains nothing except deciding which of the plugin packages is needed.
So for example, suppose we don't have a big multi-platform plugin package. Would this varying-deps feature allow you to just add one plugin dependency for each of your platforms? Or am I not understanding correctly?
NOTE Halfway through writing this I realized it sort of weakened my argument for preferring one package
Hmm well to put it in a non-abstract perspective, Couchbase Lite .NET has 11 supported platform combination targets, out of which 8 are in GA and 3 are beta:
GA .NET 4.5 Windows -> Uses net45 .NET 3.5 Windows -> Uses net35 .NET 4.5 Mono OS X -> Uses net45 .NET 3.5 Mono OS X -> Uses net35 .NET 4.5 Mono Linux -> Uses net45 .NET 3.5 Mono Linux -> Uses net35 Xamarin.iOS -> Uses Xamarin.iOS10 Xamarin.Android -> Uses MonoAndroid
Beta Unity3D Desktop -> N/A Unity3D iOS -> N/A Unity3D Android -> N/A
So for the net45 target, for example, I would want the Windows native DLLs included in whatever version I end up choosing. I think I may have jumped to conclusions that I would need to add the four different deps (iOS, net35, net45, droid) to EVERY project because I forgot about dependency target frameworks. So in the end if it can work as below, then you should use whatever configuration is easiest for you to maintain and work with.
Couchbase.Lite.Storage.SystemSQLite "mock" nuspec
Framework=net45 deps -> SQLitePCL.raw, SQLitePCL.ugly, SQLitePCL.plugin.sqlite3.whatever (statically compiled runtime, including Windows DLLs)
Framework=net35 deps -> same as net45
Framework=Xamarin.iOS10 deps -> SQLitePCL.raw, SQLitePCL.ugly, SQLitePCL.plugin.sqlite3.ios_unified (see note at bottom)
Framework=MonoAndroid deps -> SQLitePCL.raw, SQLitePCL.ugly, SQLitePCL.plugin.sqlite3.droid (see note at bottom)
Couchbase.Lite.Storage.SQLCipher (and other custom native builds) "mock" nuspec
Framework=all deps -> SQLitePCL.raw, SQLitePCL.ugly (plugin will come from inside package)
(note at bottom) This depends on whether the "default" ISQLite3Provider implementation will be part of the plugin packages, or part of the core package. If they are part of the core package, then iOS and Android plugins will not be needed, as you said.
Add on comments: I am using the deps per platform target at the moment because some nuget deps are not available for .NET 3.5, so I cannot add them for .NET 3.5 and am forced to include them in the package.
For custom SQLite, we have a repo similar to our SQLCipher one for building our own SQLite3 with the compilation flags that we need, it may or may not differ from the one you offer.
OK, so understanding that you support Mono/OSX and Mono/Linux was an "Aha moment".
But then this sentence confused me:
So for the net45 target, for example, I would want the Windows native DLLs included in whatever version I end up choosing.
To be clear:
So you want the native Windows DLLs in whichever SQLitePCL.plugin package includes net45, however:
If the targets file for that package always copies the native Windows DLLs to the project output directory, that's going to be a problem for you, right? You need that targets file to respect OSX and Linux issues.
(I assume for OSX and Linux you always rely on whatever SQLite is installed with the OS.)
Yes, your summary is correct. If the targets file copies it every time it won't be a huge problem, but it looks weird. OS X and Linux will be using the system SQLite, unless they install the custom sqlite package (that I haven't created yet) or the SQLCipher package.
Basically, the way it works now with regards to x86/sqlite3.dll and x64/sqlite3.dll is the way I'd like it to continue working:
Included in the net45 package but: Windows -> Copy OS X -> Don't copy Linux -> Don't copy
Will you be updating the SQLite binary to 3.11.0 in the 0.9.0 release? http://sqlite.org/releaselog/3_11_0.html
@ps30 Yes, I plan to do so.
0.9.x has been released
Background
Calling SQLite from C#/F#/etc involves two parts:
Calling C from managed code is rather complicated, and even more so when dealing with lots of different underlying platforms. Most platforms use the pinvoke mechanism, but they do so in different ways. And the Silverlight-flavored versions of Windows Phone do not support pinvoke, so they have to use C++/CX instead.
The goal of SQLitePCL.raw is to hide all that complexity and present a portable API for calling SQLite.
But the previous sentence makes things sound simpler than they are. Exactly what do we mean by "SQLite"? Obviously, we are referring to some compiled form of the SQLite library. But where does that come from?
Giving people choices can be a bad thing. Too many options is the way to paralysis. There is much to be said for something that Just Works without hassle.
So it is tempting to eliminate a bunch of these choices. For example, maybe we should not support the ability to use the SQLite built-in to iOS and just always bundle a newer version with all the optional features turned on?
Alas, my experience in maintaining this library and supporting the developers who use it has taught me that app developers need the ability to choose among these tradeoffs.
Two kinds of SQLite choices
It is worth highlighting at this point that all the complexity of choosing a specific SQLite build can be separated into two categories:
Two cases of developers
It is also worth mentioning that the needs of library developers are a bit different than those of app developers.
If someone is building and deploying an app, they probably care a lot about exactly how SQLite is included and what features it is using.
But if someone is developing a library which calls SQLitePCL.raw, they may not care about much of the stuff above. They probably want to defer those choices to their own users.
And I should also admit that library developers are the primary audience of this library, since it intentionally provides a "raw" API that is low-level, intended to be the foundation for friendlier APIs to be built on top of it.
The Status Quo
As of SQLitePCL.raw version 0.8.6, we have one large NuGet package which tries to solve all these problems.
With the exception of support for Windows 10 Universal apps (the "uap10.0" TFM, added in 0.8.5), the package is based on a NuGet 2.x approach, using none of the new stuff from NuGet 3.x.
The package contains both SQLitePCL.raw.dll (in a bunch of different forms) and compiled SQLite libraries (in a bunch of different forms).
For Windows platforms, the job of copying the correct sqlite3.dll to the build directory is handled by using an msbuild "targets" file, a NuGet feature wherein msbuild code from the NuGet package can be invoked as part of the build process. This targets file chooses the correct sqlite3.dll based on the CPU. On most Windows platforms, SQLitePCL.raw.dll goes into the NuGet "lib" directory, and the "targets" file goes into the NuGet "build" directory.
For the Silverlight-flavored versions of Windows Phone, because pinvoke is not used, the targets file has to do more. Everything goes into the "build" directory.
For iOS and Android, the targets file is not necessary for CPU choice. On iOS, unmanaged code libraries are "fat", containing multiple CPU architectures in a single file. On both iOS and Android, native code gets included by the Xamarin tools by embedding it into a resource within a CLR assembly.
So if we did not care about supporting different builds of SQLite for features (like full text search or encryption), we wouldn't need the targets file for iOS/Android at all. But NuGet doesn't have any way of presenting the sort of config option we would need. So, the SQLitePCL.raw.dll file(s) go into the build directory anyway, and a targets file is used to choose based on the msbuild property "UseSQLiteFrom".
Ignoring the different forms of native code for a moment, the various versions of SQLitePCL.raw.dll itself are actually not that different from each other:
Other tidbits about the status quo:
The change I am considering
I would like to do this differently.
The idea is get all the compiled SQLite libraries out of SQLitePCL.raw itself.
And the various builds of SQLite (or SQLCipher) itself would go into separate NuGet packages. In other words, if you are using the SQLitePCL.raw package, you would also need to reference something (like another NuGet package) which gives you SQLite itself.
An attractive thing about this idea is that the complexity ends up located very close to the thing that caused it. SQLitePCL.raw itself becomes simple, because it mostly is. And the plethora of SQLite builds will be, er, complicated, but that's just the way things are.
The trick is to design this so that all the flexibility above gets retained.
The key question in all of this is: In SQLitePCL.raw.dll, what name is provided to DllImport (pinvoke)?
(Let's ignore the non-pinvoke platforms for now.)
The simple answer is to just use "sqlite3":
So, using "sqlite3" with DllImport is problematic. What are the alternatives?
So,
DllImport("sqlite3")
is simple but inflexible. AndDllImport("custom name")
is flexible but complicated.I have made various attempts to find a way to allow this decision to be made at runtime, without success. If such a solution were to exist, I would set things up such that "sqlite3" would be used by default and provide a way to override.
Moving forward
I have not yet decided how to proceed. I am publishing this (lengthy and tedious) writeup to solicit feedback. If you have made it all the way to the end, please accept my thanks for your attention. All comments would be welcomed.