22. März 2023 von Vincent Scherb
Fehlerbehebung beim Open-Source-NuGet-Paket
Für ein Projekt entschloss ich mich, die libgit2-Bibliothek für die portable C#-Implementierung zu nutzen. Beim Hinzufügen des NuGet-Pakets in der aktuellen Version 0.26.2 (vom 11. Dezember 2019) trat ein Problem während der Laufzeit auf: Die freigegebene Bibliothek konnte nicht geladen werden.
In diesem Blog-Beitrag erfahrt ihr mehr über diesen Fehler. Ich erkläre die einzelnen Schritte, wie ihr dieses Problem beheben und mit eurem Projekt fortfahren könnt.
Ich erstelle zuerst ein MVP (minimal funktionsfähiges Produkt), um das Problem zu replizieren und es ohne weitere Projektabhängigkeiten zu untersuchen. Dann befolge ich den Prozess zur Fehlerbehebung und zur Suche nach einer Lösung.
Das Ganze führe ich auf Majaro x64 with .Net 6 aus.
Den Fehler im MVP replizieren
Meine Beobachtung war, dass jeder Aufruf der Bibliothek einen Fehler verursachte. Daher konnte das Problem recht einfach repliziert werden: Ich musste nur das NuGet-Paket einbinden und irgendetwas aus der Bibliothek aufrufen.
// Get the directory where the executable is run.
string currentDirectory = Directory.GetCurrentDirectory();
// Call the library to check whether the provided location is a valid
// git repository.
bool directoryIsARepository = LibGit2Sharp.Repository.IsValid(currentDirectory);
// Set the exit code to non zero to indicate an error or the uncaught
// exception will print the details.
return directoryIsARepository ? 0 : 1;
Nachdem der Fehler auf meinem Rechner nachgestellt wurde, habe ich ein neues Repository erstellt und ein Minimum an Code hinzugefügt, der zum Reproduzieren des Fehlers erforderlich ist. Das Repository wurde unter sr.ht hochgeladen.
$ git log --oneline -n1
e8f4902 Add simple check for the library
~/Projects/poc_libgit2sharp main $ make run
dotnet build ./poc_libgit2sharp/poc_libgit2sharp.csproj
Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
All projects are up-to-date for restore.
poc_libgit2sharp -> /home/vince/Projects/poc_libgit2sharp/poc_libgit2sharp/bin/Debug/net6.0/poc_libgit2sharp.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:01.33
dotnet run --project ./poc_libgit2sharp/poc_libgit2sharp.csproj
Current directory is '/home/vince/Projects/poc_libgit2sharp'
Checking whether the directory contains a valid git repository.
Should return true as the program is run from a repository.
Unhandled exception. System.TypeInitializationException: The type initializer for 'LibGit2Sharp.Core.NativeMethods' threw an exception.
---> System.DllNotFoundException: Unable to load shared library 'git2-106a5f2' or one of its dependencies. In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable: libgit2-106a5f2: cannot open shared object file: No such file or directory
at LibGit2Sharp.Core.NativeMethods.git_libgit2_init()
at LibGit2Sharp.Core.NativeMethods.InitializeNativeLibrary()
at LibGit2Sharp.Core.NativeMethods..cctor()
--- End of inner exception stack trace ---
at LibGit2Sharp.Core.NativeMethods.git_repository_open_ext(git_repository*& repository, FilePath path, RepositoryOpenFlags flags, FilePath ceilingDirs)
at LibGit2Sharp.Core.Proxy.git_repository_open_ext(String path, RepositoryOpenFlags flags, String ceilingDirs)
at LibGit2Sharp.Repository.IsValid(String path)
at Program.<Main>$(String[] args) in /home/vince/Projects/poc_libgit2sharp/poc_libgit2sharp/Program.cs:line 7
make: *** [Makefile:18: run] Error 134
Zuerst sollten wir uns die zurückgegebene Fehlermeldung genauer anschauen, um das Grundproblem zu verstehen. Erst danach ergreifen wir weitere Maßnahmen und analysieren Code des Open-Source-Repositorys libgit2.
Unhandled exception. System.TypeInitializationException:
The type initializer for 'LibGit2Sharp.Core.NativeMethods' threw an exception.
---> System.DllNotFoundException:
Unable to load shared library 'git2-106a5f2' or one of its dependencies.
In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable:
libgit2-106a5f2: cannot open shared object file: No such file or directory
Wir wissen, dass die Bibliothek ein Wrapper um die C-Implementierung von libgit2 ist, um sie in der verwalteten Welt von .NET nutzen zu können. Mit diesem Hintergrundwissen können wir also annehmen, dass es bei der Referenz zu git2-106a5f2
von System.DllNotFoundException
um die native Bibliothek geht. Im nächsten Schritt wird ermittelt, von wo aus die Bibliothek geladen wird und wo der Fehler auftritt. Mit diesen Informationen kann man das Problem besser nachvollziehen.
Die Untersuchung mit Debug-Optionen
Die Fehlermeldung liefert uns alle Informationen, die wir für das weitere Vorgehen benötigen. Sie gibt an, dass LD_DEBUG
genutzt wird. Sehen wir uns also die Ausgabe mit dieser Umgebungsvariablen an (Ausgabe stark gekürzt):
~/Projects/poc_libgit2sharp main $ LD_DEBUG=libs make run
dotnet run --project ./poc_libgit2sharp/poc_libgit2sharp.csproj
1022222: initialize program: /home/vince/Projects/poc_libgit2sharp/poc_libgit2sharp/bin/Debug/net6.0/poc_libgit2sharp
Current directory is '/home/vince/Projects/poc_libgit2sharp'
Checking whether the directory contains a valid git repository.
Should return true as the program is run from a repository.
1022222: find library=git2-106a5f2.so [0]; searching
1022222: search cache=/etc/ld.so.cache
1022222: search path=/usr/lib/tls:/usr/lib (system search path)
1022222: trying file=/usr/lib/tls/git2-106a5f2.so
1022222: trying file=/usr/lib/git2-106a5f2.so
1022222:
1022222: [...]
1022222: find library=libgit2-106a5f2.so [0]; searching
1022222: search cache=/etc/ld.so.cache
1022222: search path=/usr/lib/tls:/usr/lib (system search path)
1022222: trying file=/usr/lib/tls/libgit2-106a5f2.so
1022222: trying file=/usr/lib/libgit2-106a5f2.so
1022222:
1022222: [...]
1022222: find library=git2-106a5f2 [0]; searching
1022222: search cache=/etc/ld.so.cache
1022222: search path=/usr/lib/tls:/usr/lib (system search path)
1022222: trying file=/usr/lib/tls/git2-106a5f2
1022222: trying file=/usr/lib/git2-106a5f2
1022222:
1022222: find library=libgit2-106a5f2 [0]; searching
1022222: search cache=/etc/ld.so.cache
1022222: search path=/usr/lib/tls:/usr/lib (system search path)
1022222: trying file=/usr/lib/tls/libgit2-106a5f2
1022222: trying file=/usr/lib/libgit2-106a5f2
1022222:
Unhandled exception.
System.TypeInitializationException: The type initializer for 'LibGit2Sharp.Core.NativeMethods' threw an exception.
---> System.DllNotFoundException: Unable to load shared library 'git2-106a5f2' or one of its dependencies.
[...]
make: *** [Makefile:18: run] Error 134
Wird der Parameter LD_DEBUG
auf all
oder files
festgelegt, erhalten wir ähnliche Ergebnisse:
1022654: file=/home/vince/Projects/poc_libgit2sharp/poc_libgit2sharp/bin/Debug/net6.0/runtimes/linux-x64/native/git2-106a5f2.so [0]; dynamically loaded by /usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.2/libcoreclr.so [0]
1022654:
1022654: file=/usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.2/git2-106a5f2.so [0]; dynamically loaded by /usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.2/libcoreclr.so [0]
1022654:
1022654: file=/home/vince/Projects/poc_libgit2sharp/poc_libgit2sharp/bin/Debug/net6.0/git2-106a5f2.so [0]; dynamically loaded by /usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.2/libcoreclr.so [0]
1022654:
1022654: file=git2-106a5f2.so [0]; dynamically loaded by /usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.2/libcoreclr.so [0]
[...]
1022654: file=/home/vince/Projects/poc_libgit2sharp/poc_libgit2sharp/bin/Debug/net6.0/runtimes/linux-x64/native/libgit2-106a5f2.so [0]; dynamically loaded by /usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.2/libcoreclr.so [0]
1022654: file=/home/vince/Projects/poc_libgit2sharp/poc_libgit2sharp/bin/Debug/net6.0/runtimes/linux-x64/native/libgit2-106a5f2.so [0]; generating link map
1022654: dynamic: 0x00007fda6ad24d90 base: 0x00007fda6aa00000 size: 0x000000000032a5d0
1022654: entry: 0x00007fda6aa166f0 phdr: 0x00007fda6aa00040 phnum: 7
Die Bibliotheksdateien ./bin/Debug/net6.0/runtimes/linux-x64/native/libgit2-106a5f2.so
sind vorhanden und sie werden korrekt importiert. Aber die referenzierte Datei /usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.2/git2-106a5f2.so
ist unter diesem globalen Speicherort nicht zu finden.
Wenn keine spezifische Version auf die gemeinsam genutzte Objektdatei angewendet wird, sollte die Datei meiner Meinung nach vom System stammen. Wir prüfen den Paket-Manager und installieren die aktuelle Version der zugrunde liegenden Bibliothek libgit2
auf dem System.
$ sudo pacman -S libgit2
Aber sogar nach der Installation und Aktualisierung tritt der Fehler weiterhin auf. Wie bereits vermutet, gibt es keinen Zusammenhang mit der globalen Installation der Bibliothek. Sie sollte mit dem NuGet-Paket ausgeliefert werden.
Der nächste Schritt ist also, die Probleme auf GitHub zu untersuchen und eine mögliche Lösung zu finden.
Die GitHub-Fehler auf mögliche Lösungen hin untersuchen
Vor der Analyse der Installationsscripts oder des Bibliothek-Codes untersuchen wir die Fehler auf GitHub.Einige Probleme deuten darauf hin, dass die korrekte Abhängigkeit des Pakets verwendet werden soll. Das ist LibGit2Sharp.NativeBinaries
in Version 2.0.306. Das war bereits der Fall, als die Datei sobj/project.assets.json
überprüft wurde. Mehrere Probleme weisen allerdings darauf hin, dass es Probleme mit .Net 5 gibt, die in der Preview-Version 0.27.0des NuGet-Pakets LibGit2Sharp behoben werden sollten.
Die Preview-Version der Bibliothek installieren
Aktualisieren wir jetzt die Paketreferenz, um zu prüfen, ob die Preview-Version das Problem für die .Net-6-Anwendung
behebt.
<ItemGroup>
<PackageReference Include="LibGit2Sharp" Version="0.27.0-preview-0182" />
</ItemGroup>
~/Projects/poc_libgit2sharp main $ make run
[...]
dotnet run --project ./poc_libgit2sharp/poc_libgit2sharp.csproj
Current directory is '/home/vince/Projects/poc_libgit2sharp'
Checking whether the directory contains a valid git repository.
Should return true as the program is run from a repository.
This message indicates that no exception was thrown and the library works as expected.
The directory '/home/vince/Projects/poc_libgit2sharp' is a git repository 'True'
Perfekt! Es wird kein Ausnahmefehler von der Bibliothek zurückgegeben und das erwartete Ergebnis ist korrekt. Das Arbeitsverzeichnis ist tatsächlich ein git repository.
Eine Referenz auf eine Preview-Version ist allerdings immer unangenehm und sollte vermieden werden. Von Git selbst wissen wir, dass wir die Quellen direkt einbinden und sie in unser Projekt integrieren können. Aber was heißt das für unser Projekt? Das wollen wir in diesem MVP mit beiden Optionen herausfinden.
Erstellung aus der Quelle
Im letzten Schritt versuchen wir nun, das Projekt selbst zu erstellen und das Ergebnis in unser Projekt einzubinden. Mit dieser Option kann der master
-Zweig des Repositorys direkt verknüpft und entsprechend aktualisiert werden. So erhalten wir die aktuellen Updates und müssen auf kein Update des NuGet-Pakets warten.
$ git submodule add https://github.com/libgit2/libgit2sharp.git libgit2sharp
[...]
$ ls -l
-rw-r--r-- 1 vince vince 349 Apr 21 20:23 .gitignore
-rw-r--r-- 1 vince vince 99 Apr 21 22:16 .gitmodules
[...]
drwxr-xr-x 9 vince vince 4096 Apr 21 22:24 libgit2sharp
-rw-r--r-- 1 vince vince 432 Apr 21 20:54 Makefile
drwxr-xr-x 4 vince vince 4096 Apr 21 20:21 poc_libgit2sharp
$ cat .gitmodules
[submodule "libgit2sharp"]
path = libgit2sharp
url = https://github.com/libgit2/libgit2sharp.git
Jetzt kann endlich die Projektreferenz aktualisiert werden.
$ git diff d85adbb~..d85adbb
diff --git a/poc_libgit2sharp/poc_libgit2sharp.csproj b/poc_libgit2sharp/poc_libgit2sharp.csproj
index 4329b2b..f3f8c62 100644
--- a/poc_libgit2sharp/poc_libgit2sharp.csproj
+++ b/poc_libgit2sharp/poc_libgit2sharp.csproj
@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="LibGit2Sharp" Version="0.27.0-preview-0182" />
+ <ProjectReference Include="..\libgit2sharp\LibGit2Sharp\LibGit2Sharp.csproj" />
</ItemGroup>
</Project
Das Submodul funktioniert wie erwartet, so wie bei der Preview-Version des NuGet-Pakets. Beim Submodul treten ähnliche Nachteile auf wie bei der Verwendung eines Paket-Managers: Die Referenz muss manuell aktualisiert werden, wenn eine neue Version veröffentlicht wird.
Wenn das Submodul verwendet wird, kann der master
-Zweig direkt ausgecheckt und auf die neueste Entwicklungsversion geprüft werden. Ganz wichtig: Wenn ein stabiles Release von 0.27.0
des Pakets veröffentlicht wird, muss diese Option neu bewertet werden.
Fazit
Um alle Änderungen anzuzeigen, die seit der letzten veröffentlichten Version vorgenommen wurden, können wir den Unterschied auf GitHub v0.26..master direkt auschecken.
Die Hilfe zu Submodulen ist umfangreich. Sie kann unter man 1 git-submodule
und man 7 gitsubmodules
abgerufen werden.
Bei meinem Projekt habe ich mich für den Submodul-Workaround entschieden, weil dieser einige Vorteile hat:
- Durch das regelmäßige Erstellen kann ich das Submodul auf Aktualisierungen hin überwachen.
- Ich bleibe bei Änderungen an der Bibliothek auf dem Laufenden.
- Ich kann den Entwicklerinnen und Entwicklern der Bibliothek Feedback geben, da ich mit einer Version arbeite, die neue Features und Fehlerbehebungen enthält.
- Ich kann auf verschiedenen Zweigen verschiedene Versionen der Bibliothek testen.