My matplotlib code is failing in ubuntu VMs in Azure DevOps; whereas the same code works in Azure Windows VMs and on my own ubuntu VM. This boiled-down Azure pipeline YAML reproduces the issue:
trigger:
- none
pool:
vmImage: ubuntu-18.04
variables:
python.version: '3.7'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
addToPath: true
displayName: 'Use Python $(python.version)'
- bash: |
pip install --upgrade pip &&
pip install matplotlib &&
pip list
displayName: 'Install required packages'
- bash: |
sudo apt-get install python3-tk
displayName: 'Install Tcl/Tk GUI toolkit'
- task: PythonScript@0
inputs:
scriptSource: inline
script: |
import tkinter
print('Tcl/Tk version: {}'.format(tkinter.Tcl().eval('info patchlevel')))
displayName: 'Report Tcl/Tk version'
- task: PythonScript@0
inputs:
scriptSource: inline
script: |
import matplotlib
import matplotlib.pyplot as plt
print("Using: " matplotlib.get_backend())
plt.rcParams['toolbar'] = 'toolmanager'
fig = plt.figure()
ax = fig.add_subplot(111)
tm = fig.canvas.manager.toolmanager
tm.remove_tool('help')
displayName: 'Remove help button from toolbar'
When the pipeline runs, it reports matplotlib 3.5.2 and Tcl/Tk version 8.6.8 are installed on the VM. But the final task fails with:
/home/vsts/work/_temp/5e3dce70-dff2-405e-b5ef-8dac88d22243.py:6: UserWarning: Treat the new Tool classes introduced in v1.5 as experimental for now; the API and rcParam may change in future versions.
plt.rcParams['toolbar'] = 'toolmanager'
/home/vsts/work/_temp/5e3dce70-dff2-405e-b5ef-8dac88d22243.py:13: UserWarning: ToolManager does not control tool help
tm.remove_tool('help')
Using: agg
Traceback (most recent call last):
File "/home/vsts/work/_temp/5e3dce70-dff2-405e-b5ef-8dac88d22243.py", line 13, in <module>
tm.remove_tool('help')
File "/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/site-packages/matplotlib/backend_managers.py", line 210, in remove_tool
tool.destroy()
AttributeError: 'NoneType' object has no attribute 'destroy'
Note the printed backend for matplotlib is agg
rather than TkAgg
. The same Python code works in my own ubuntu 18.04 VM after running sudo apt-get install python3-tk
, and the code then reports TkAgg
there.
So why doesn't this work in ubuntu in Azure DevOps? It looks like Tcl/Tk is not being installed or configured properly in the ubuntu VM.
Update
If I insert matplotlib.use('TkAgg')
after import matplotlib
then the output does say Using: TkAgg
. But I get this error instead at the fig = plt.figure()
line:
ImportError: Cannot load backend 'TkAgg' which requires the 'tk' interactive framework, as 'headless' is currently running
I've tried setting the environment variables DISPLAY and MPLBACKEND as well, but to no avail.
CodePudding user response:
It turns out I needed to create a virtual display for Tcl/Tk to work correctly on the remote Azure DevOps VMs. I used Xvfb, as that's already installed on the ubuntu 18.04 agent (as per here). You also need to set the DISPLAY
environment variable to point at the virtual display. Here's what worked for me in the end:
trigger:
- none
pool:
vmImage: ubuntu-18.04
variables:
python.version: '3.7'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
addToPath: true
displayName: 'Use Python $(python.version)'
- bash: |
pip install matplotlib &&
pip list
displayName: 'Install required packages'
- task: PythonScript@0
inputs:
scriptSource: inline
script: |
import tkinter
print('Tcl/Tk version: {}'.format(tkinter.Tcl().eval('info patchlevel')))
displayName: 'Report Tcl/Tk version'
- bash: |
Xvfb :1 -screen 0 640x480x16 &
displayName: 'Create virtual display'
- bash: |
echo "##vso[task.setvariable variable=DISPLAY]:1.0"
displayName: 'Set DISPLAY'
- task: PythonScript@0
inputs:
scriptSource: inline
script: |
import matplotlib
import matplotlib.pyplot as plt
print("Using: " matplotlib.get_backend())
plt.rcParams['toolbar'] = 'toolmanager'
fig = plt.figure()
ax = fig.add_subplot(111)
tm = fig.canvas.manager.toolmanager
tm.remove_tool('help')
displayName: 'Remove help button from toolbar'
The output of the final task was then:
/home/vsts/work/_temp/b7d18caa-56d0-4438-9d8c-b6bbaa04935b.py:10: UserWarning: Treat the new Tool classes introduced in v1.5 as experimental for now; the API and rcParam may change in future versions.
plt.rcParams['toolbar'] = 'toolmanager'
Using: TkAgg
Note matplotlib automatically detects the TkAgg backend; you don't have to tell it to use it. Two gotchas that caught me out: Xvfb starts with a capital X; and make sure you run it in the background!