0

I am using pythonnet to call .NET dlls from Python. My code all works on my local machine, but it's failing when I unit test on the BitBucket server.

First, I have a step that builds a dummy .NET 6 exe in order to trigger download of the nuget packages. That's running image: mcr.microsoft.com/dotnet/sdk:6.0 and creates an artifact to use in the next step.

Then I set up the pythonnet environment on image image: python:3.11:

        - if [ -f unit_test_requirements.txt ]; then pip install -r unit_test_requirements.txt; fi
        - export PYTHONPATH=$PYTHONPATH:$BITBUCKET_CLONE_DIR/ctop:$ARCHIVE
        # Pure python tests that run fine here...
        - wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh
        - chmod +x ./dotnet-install.sh
        - ./dotnet-install.sh --runtime dotnet --version latest
        - export DOTNET_ROOT=$HOME/.dotnet
        - export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools
        - export PYTHONPATH=$PYTHONPATH:$DOTNET_ROOT:$DOTNET_ROOT/tools

pythonnet 3.0.1 installs

Collecting pythonnet (from -r unit_test_requirements.txt (line 3))
  Using cached pythonnet-3.0.1-py3-none-any.whl (284 kB)

python -c "import sys; print(sys.path)" prints the expected folders.

The errors I was getting in unit testing were buried deep in the import tree, so I simplified. I can load my own Dll that has a dependency on System.Text.Json 2 levels deep. I can load classes from System namespace. System.Text.Json exists in my archived folder, but when I try to load System.Text.Json, I get an error. The System.Text.Json in my solution is 7.0.3, the version of the file in the folder says 7.0.723.27404, but ILSpy reports the version (7.0.0.0) and token matching the error message.

+ python -c "from pathlib import Path;print(Path('.../My.A.dll').exists())"
True

+ python -c "from pythonnet import load;load('coreclr');import clr;clr.AddReference('My.A');from My.A.Namespace import B"

+ python -c "from pythonnet import load;load('coreclr');import clr;from System import Guid, String, Action"

+ python -c "from pathlib import Path;print(Path('.../System.Text.Json.dll').exists())"
True

+ python -c "from pythonnet import load;load('coreclr');import clr;clr.AddReference('System.Text.Json');import clr;from System.Text.Json import JsonConverter"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
System.IO.FileLoadException: Could not load file or assembly 'System.Text.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.

I downloaded the artifact to my local PC, changed my local pythonpath to point there, and all these commands pass fine on my local PC, with fairly robust unit testing working on local. So it's not the artifact itself. (Yes, in a fresh console window started after I changed the env variables.)

I don't think it's a .NET version problem. I'm using pythonnet 3.0.1, so it's not a problem with PythonNet 2.x not supporting core. System.Json.Text 7.0.3 supports .NET 6, .NET Standard 2.0, and .NET Framework 4.6.2, so it should load in any case. I am doing all the steps to load core. I'm getting .NET 6 installed in my steps:

+ ./dotnet-install.sh --runtime dotnet --version latest
...
dotnet-install: Installed version is 6.0.21
...
dotnet-install: Installation finished successfully.

I have a second Windows environment also getting similar errors, but I don't have direct access to it, so I'm tackling the CI/Bitbucket environment first, hoping it's a common root issue.

Denise Skidmore
  • 2,286
  • 22
  • 51
  • $DOTNET_ROOT/tools was recommended addition to the path somewhere, but I don't think that folder actually exists. A little digging around didn't turn up any folder inside .dotnet root named tools. – Denise Skidmore Aug 17 '23 at 03:26
  • Possibly related issue: https://github.com/pythonnet/pythonnet/discussions/2217 – Denise Skidmore Aug 17 '23 at 03:36
  • Earlier iteration of the same issue, with less granular assembly load investigation, but more image setups tried. https://github.com/pythonnet/pythonnet/discussions/2194 – Denise Skidmore Aug 17 '23 at 03:38

2 Answers2

1

You might need to pass runtimeconfig parameter to tell .NET how to look up package dependencies. E.g. load("coreclr", runtimeconfig="path.runtimeconfig").

.NET 7 works for you probably because all System.Json.Text 7.0 dependencies are bundled with it.

See https://github.com/pythonnet/pythonnet/blob/3b6500fbdc844096665a24ba07f5a56c11192e1b/doc/source/python.rst#calling-pythonnetload

LOST
  • 2,956
  • 3
  • 25
  • 40
  • Other parts of our team are moving to .NET 7, so it's an acceptable solution for me. Another option I have is to groom my dependency tree back to version 6.* packages. – Denise Skidmore Aug 17 '23 at 19:10
  • My project that downloads the nugets does generate a runtimeconfig.json, so it would not be a lot of handwork to generate one, but I would have the new problem of finding the runtimeconfig relative to my script, which is not the same in all environments. Destination environments need a runtime installed anyway, pointing them to one that solves all issues is helpful. – Denise Skidmore Aug 17 '23 at 19:14
  • I could just copy a working runtimeconfig.json local to the script, but I'd like it to just work with the app it gets the dlls from in production, and that we set up with pythonpath. – Denise Skidmore Aug 17 '23 at 19:47
0

My Runtime was wrong version. System.Text.Json 7.0 SAYS it's .NET 6 compatible, but my problem went away when installing .NET 7. I also changed the install method, but I did a test with the new install method and .net 6 and it also failed.

Fix process:

A warning message sent me down an OS specific install path.

dotnet-install: Note that the script does not resolve dependencies during installation.
dotnet-install: To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the "Dependencies" section.

The install instructions needed the specific linux distribution, so I got that:

+ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"

Then used the OS specific install instructions https://learn.microsoft.com/en-us/dotnet/core/install/linux-debian#debian-12 (modified slightly for minimum install and bitbucket environment)

        - wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
        - dpkg -i packages-microsoft-prod.deb
        - rm packages-microsoft-prod.deb
        - apt-get update && apt-get install -y dotnet-runtime-7.0

This made dotnet root move (as shown by dotnet --list-runtimes) so I had to update export DOTNET_ROOT=/usr/share/dotnet/

Denise Skidmore
  • 2,286
  • 22
  • 51