diff --git a/Directory.Build.props b/Directory.Build.props index d00d035c..a6a75b3a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -39,7 +39,12 @@ - ManagedOnly + ManagedOnly + 1.5.5 + + $(MSBuildThisFileDirectory)artifacts\natives\$(DuckDbVersion)\ This package does not include a copy of the native DuckDB library. diff --git a/DuckDB.NET.Benchmarks/Benchmarks.csproj b/DuckDB.NET.Benchmarks/Benchmarks.csproj index 2de0564f..8ced6dcd 100644 --- a/DuckDB.NET.Benchmarks/Benchmarks.csproj +++ b/DuckDB.NET.Benchmarks/Benchmarks.csproj @@ -20,14 +20,4 @@ - - - - false - PreserveNewest - runtimes\%(RecursiveDir)\%(FileName)%(Extension) - - - diff --git a/DuckDB.NET.Benchmarks/NativeLibraryLoader.cs b/DuckDB.NET.Benchmarks/NativeLibraryLoader.cs deleted file mode 100644 index 429de245..00000000 --- a/DuckDB.NET.Benchmarks/NativeLibraryLoader.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace DuckDB.NET.Benchmarks; - -/// -/// Loads the platform-native DuckDB library for the benchmark process. -/// -internal static class NativeLibraryLoader -{ - [ModuleInitializer] - public static void Init() - { - if (GetRid() is not { } rid) - { - return; - } - - _ = NativeLibrary.TryLoad(Path.Join("runtimes", rid, "native", "duckdb"), Assembly.GetExecutingAssembly(), DllImportSearchPath.AssemblyDirectory, out _) || - NativeLibrary.TryLoad(Path.Join("runtimes", rid, "native", "libduckdb"), Assembly.GetExecutingAssembly(), DllImportSearchPath.AssemblyDirectory, out _); - } - - private static string? GetRid() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Environment.Is64BitProcess ? "win-x64" : "win-x86"; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return RuntimeInformation.ProcessArchitecture switch - { - Architecture.X64 => "linux-x64", - Architecture.Arm64 => "linux-arm64", - _ => null, - }; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return "osx"; - } - - return null; - } -} diff --git a/DuckDB.NET.Bindings/Bindings.csproj b/DuckDB.NET.Bindings/Bindings.csproj index 3ba02773..d99b9617 100644 --- a/DuckDB.NET.Bindings/Bindings.csproj +++ b/DuckDB.NET.Bindings/Bindings.csproj @@ -3,7 +3,7 @@ DuckDB Bindings for C#. -- Updated to DuckDB v1.5.5 +- Updated to DuckDB v$(DuckDbVersion) New features: - Added bindings for the Arrow C Data Interface (duckdb_to_arrow_schema, duckdb_data_chunk_to_arrow, duckdb_result_get_arrow_options) @@ -11,7 +11,7 @@ New features: DuckDB.NET.Native win-x64;win-arm64;linux-x64;linux-arm64;osx - https://github.com/duckdb/duckdb/releases/download/v1.5.5 + https://github.com/duckdb/duckdb/releases/download/v$(DuckDbVersion) True ..\keyPair.snk true @@ -23,7 +23,16 @@ New features: - + + + + <_PackDuckDbNatives Condition="'$(BuildType)' == 'Full' ">true + <_PackDuckDbNatives Condition="'$(_PackDuckDbNatives)' == ''">false + + + <_NativeLib Include="win-x64" LibUrl="$(DuckDbArtifactRoot)/libduckdb-windows-amd64.zip" /> <_NativeLib Include="win-arm64" LibUrl="$(DuckDbArtifactRoot)/libduckdb-windows-arm64.zip" /> @@ -36,20 +45,29 @@ New features: <_NativeLib Include="linux-x64" LibUrl="$(DuckDbArtifactRoot)/duckdb-binaries-linux-amd64.zip" InnerZipName="libduckdb-linux-amd64.zip" Condition="$([MSBuild]::IsOSPlatform('Linux'))" /> <_NativeLib Include="osx" LibUrl="$(DuckDbArtifactRoot)/duckdb-binaries-osx.zip" InnerZipName="libduckdb-osx-universal.zip" Condition="$([MSBuild]::IsOSPlatform('OSX'))" /> - + - + - - + + + false PreserveNewest - true + $(_PackDuckDbNatives) \runtimes - runtimes\%(RecursiveDir)\%(FileName)%(Extension) - + runtimes\%(RecursiveDir)%(FileName)%(Extension) + + + + diff --git a/DuckDB.NET.Bindings/DownloadNativeLibs.targets b/DuckDB.NET.Bindings/DownloadNativeLibs.targets index 0a73e20c..0c89ee23 100644 --- a/DuckDB.NET.Bindings/DownloadNativeLibs.targets +++ b/DuckDB.NET.Bindings/DownloadNativeLibs.targets @@ -1,47 +1,77 @@ - - - - - - $(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native.zip - - - - - - + + + + $(DuckDbNativeRoot)$(Rid) + $(RidDir)\native + $(RidDir)\native.zip + $(RidDir)\temp + $(RidDir)\duckdb.inputs.cache + $(RidDir)\duckdb.download.cache + + + + + + + + + + + + - - - - $(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\native.zip - $(MSBuildProjectDirectory)\obj\runtimes\$(Rid)\temp - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDB.NET.Bindings/DuckDBNativeLibrary.cs b/DuckDB.NET.Bindings/DuckDBNativeLibrary.cs new file mode 100644 index 00000000..9fa0a3a9 --- /dev/null +++ b/DuckDB.NET.Bindings/DuckDBNativeLibrary.cs @@ -0,0 +1,74 @@ +using System.IO; +using System.Reflection; + +namespace DuckDB.NET.Native; + +/// +/// Resolves the native DuckDB library from the runtimes folder beside this assembly. +/// +/// +/// A NuGet package writes a runtimeTargets entry into deps.json and the host probes +/// runtimes/{rid}/native on its own. A project reference produces no such entry, so without this +/// resolver every project that consumes DuckDB.NET by reference has to load the library itself. +/// +internal static class DuckDBNativeLibrary +{ + // CA2255 warns against ModuleInitializer in a library. Registering a DllImport resolver is the + // case the rule exempts: the resolver has to be in place before the first P/Invoke, and those + // live on nested types whose static constructors the outer type cannot hook. +#pragma warning disable CA2255 + [ModuleInitializer] +#pragma warning restore CA2255 + internal static void Register() => + NativeLibrary.SetDllImportResolver(typeof(DuckDBNativeLibrary).Assembly, Resolve); + + private static IntPtr Resolve(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (libraryName != NativeMethods.DuckDbLibrary || GetRuntimeIdentifier() is not { } rid) + { + return IntPtr.Zero; + } + + // The four-argument overload applies the platform naming convention, so "duckdb" matches + // libduckdb.dylib, libduckdb.so and duckdb.dll. The two-argument one needs an exact path. + // Passing a relative path keeps this working under single-file publish, where + // Assembly.Location is empty. + foreach (var candidate in (string[])["duckdb", "libduckdb"]) + { + var path = Path.Combine("runtimes", rid, "native", candidate); + + if (NativeLibrary.TryLoad(path, assembly, DllImportSearchPath.AssemblyDirectory, out var handle)) + { + return handle; + } + } + + // Zero hands the name back to the default probing, so a library already on the system or + // resolved from a NuGet package still loads. + return IntPtr.Zero; + } + + /// + /// These identifiers name the folders the build stages, not the full RID graph. DuckDB ships one + /// universal macOS binary, so that folder is "osx" rather than osx-x64 and osx-arm64. + /// + private static string? GetRuntimeIdentifier() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Environment.Is64BitProcess ? "win-x64" : "win-x86"; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return RuntimeInformation.ProcessArchitecture switch + { + Architecture.X64 => "linux-x64", + Architecture.Arm64 => "linux-arm64", + _ => null, + }; + } + + return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx" : null; + } +} diff --git a/DuckDB.NET.Bindings/NativeMethods/NativeMethods.All.cs b/DuckDB.NET.Bindings/NativeMethods/NativeMethods.All.cs index 51183c92..aa935918 100644 --- a/DuckDB.NET.Bindings/NativeMethods/NativeMethods.All.cs +++ b/DuckDB.NET.Bindings/NativeMethods/NativeMethods.All.cs @@ -2,7 +2,7 @@ public static partial class NativeMethods { - private const string DuckDbLibrary = "duckdb"; + internal const string DuckDbLibrary = "duckdb"; //Grouped according to https://duckdb.org/docs/archive/0.8.1/api/c/api } \ No newline at end of file diff --git a/DuckDB.NET.Data/Data.csproj b/DuckDB.NET.Data/Data.csproj index 15b11e3c..5096979a 100644 --- a/DuckDB.NET.Data/Data.csproj +++ b/DuckDB.NET.Data/Data.csproj @@ -3,7 +3,7 @@ DuckDB ADO.NET Provider for C#. -- Updated to DuckDB v1.5.5 +- Updated to DuckDB v$(DuckDbVersion) New features: - Apache Arrow result streaming via DuckDBCommand.ExecuteArrowStream and ExecuteArrowBatchesAsync (#26) diff --git a/DuckDB.NET.Samples/Program.cs b/DuckDB.NET.Samples/Program.cs index f575c49c..0d1f6297 100644 --- a/DuckDB.NET.Samples/Program.cs +++ b/DuckDB.NET.Samples/Program.cs @@ -1,7 +1,6 @@ using Dapper; using DuckDB.NET.Data; using DuckDB.NET.Native; -using DuckDB.NET.Test.Helpers; using System; using System.Data.Common; using System.Diagnostics; @@ -15,12 +14,6 @@ class Program { static void Main(string[] args) { - if (!NativeLibraryHelper.TryLoad()) - { - Console.Error.WriteLine("native assembly not found"); - return; - } - DapperSample(); AdoNetSamples(); diff --git a/DuckDB.NET.Samples/Samples.csproj b/DuckDB.NET.Samples/Samples.csproj index c22e1f3e..ad364db8 100644 --- a/DuckDB.NET.Samples/Samples.csproj +++ b/DuckDB.NET.Samples/Samples.csproj @@ -9,10 +9,6 @@ Full - - - - diff --git a/DuckDB.NET.Test/Helpers/NativeLibraryHelper.cs b/DuckDB.NET.Test/Helpers/NativeLibraryHelper.cs deleted file mode 100644 index 80cb8411..00000000 --- a/DuckDB.NET.Test/Helpers/NativeLibraryHelper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using System.Runtime.InteropServices; - -namespace DuckDB.NET.Test.Helpers; - -static class NativeLibraryHelper -{ - public static bool TryLoad() - { - if (GetRid() is not { } rid) - { - return false; - } - - return NativeLibrary.TryLoad(Path.Join("runtimes", rid, "native", "duckdb"), Assembly.GetExecutingAssembly(), DllImportSearchPath.AssemblyDirectory, out _) || - NativeLibrary.TryLoad(Path.Join("runtimes", rid, "native", "libduckdb"), Assembly.GetExecutingAssembly(), DllImportSearchPath.AssemblyDirectory, out _); - } - - private static string GetRid() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Environment.Is64BitProcess ? "win-x64" : "win-x86"; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return RuntimeInformation.ProcessArchitecture switch - { - Architecture.X64 => "linux-x64", - Architecture.Arm64 => "linux-arm64", - _ => null, - }; - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return "osx"; - } - - return null; - } -} \ No newline at end of file diff --git a/DuckDB.NET.Test/ModuleInit.cs b/DuckDB.NET.Test/ModuleInit.cs index a9b77c3e..230f8901 100644 --- a/DuckDB.NET.Test/ModuleInit.cs +++ b/DuckDB.NET.Test/ModuleInit.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using DuckDB.NET.Test.Helpers; #nullable enable namespace DuckDB.NET.Test; @@ -8,7 +7,6 @@ public static class ModuleInit [ModuleInitializer] public static void Init() { - NativeLibraryHelper.TryLoad(); AssertionOptions.AssertEquivalencyUsing(options => options.Using(new DateTimeOffsetTimeComparer())); } diff --git a/DuckDB.NET.Test/Test.csproj b/DuckDB.NET.Test/Test.csproj index 37ced068..9eb1d118 100644 --- a/DuckDB.NET.Test/Test.csproj +++ b/DuckDB.NET.Test/Test.csproj @@ -26,16 +26,4 @@ - - - - - - false - PreserveNewest - \runtimes - runtimes\%(RecursiveDir)\%(FileName)%(Extension) - - - diff --git a/README.md b/README.md index ea5f8b02..181d1832 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,18 @@ using var duckDBConnection = new DuckDBConnection("DataSource=md:{your_database} If you want to build DuckDB extensions with C#, see [Giorgi/DuckDB.ExtensionKit](https://github.com/Giorgi/DuckDB.ExtensionKit). +## Updating the DuckDB version + +Set `DuckDbVersion` in `Directory.Build.props`: + +```xml +1.5.5 +``` + +That property drives the download URL, the staging path, the download cache, and the release notes in both packages. Build once and the new engine downloads. + +Each version is staged in its own folder under `artifacts/natives/`, so a previous version stays on disk and switching back downloads nothing. To try a version without editing the file, pass `-p:DuckDbVersion=1.4.1` to `dotnet build`. + ## Known Issues When debugging your project that uses DuckDB.NET library, you may get the following error: **System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt**. The error happens due to debugger interaction with the native memory. For a workaround check out [Debugger Options mess up debugging session during Marshalling