mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-11-27 17:00:54 +01:00
Improved error handling on social plugin (#6818)
* fix(social): CairoSVG OSError handling in social plugin Related issue: #6817 Co-authored-by: Guts <1596222+Guts@users.noreply.github.com> * feat(docs): Add troubleshooting guide for CairoSVG crash --------- Co-authored-by: Kamil Krzyśków <kamilzary@gmail.com> Co-authored-by: Guts <1596222+Guts@users.noreply.github.com> Co-authored-by: Martin Donath <martin.donath@squidfunk.com>
This commit is contained in:
parent
abfac1a93e
commit
a2cb35d4c5
@ -134,3 +134,127 @@ The following environments come with a preinstalled version of [pngquant]:
|
|||||||
[pngquant]: https://pngquant.org/
|
[pngquant]: https://pngquant.org/
|
||||||
[built-in optimize plugin]: ../../plugins/optimize.md
|
[built-in optimize plugin]: ../../plugins/optimize.md
|
||||||
[pngquant-winbuild]: https://github.com/jibsen/pngquant-winbuild
|
[pngquant-winbuild]: https://github.com/jibsen/pngquant-winbuild
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Cairo library was not found
|
||||||
|
|
||||||
|
After following the installation guide above it may happen that you still get
|
||||||
|
the following error:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
no library called "cairo-2" was found
|
||||||
|
no library called "cairo" was found
|
||||||
|
no library called "libcairo-2" was found
|
||||||
|
cannot load library 'libcairo.so.2': error 0x7e. Additionally, ctypes.util.find_library() did not manage to locate a library called 'libcairo.so.2'
|
||||||
|
cannot load library 'libcairo.2.dylib': error 0x7e. Additionally, ctypes.util.find_library() did not manage to locate a library called 'libcairo.2.dylib'
|
||||||
|
cannot load library 'libcairo-2.dll': error 0x7e. Additionally, ctypes.util.find_library() did not manage to locate a library called 'libcairo-2.dll'
|
||||||
|
```
|
||||||
|
|
||||||
|
This means that the [`cairosvg`][PyPi CairoSVG] package was installed, but the
|
||||||
|
underlying [`cairocffi`][PyPi CairoCFFI] dependency couldn't [find][cffi-dopen]
|
||||||
|
the installed library. Depending on the operating system the library lookup
|
||||||
|
process is different:
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
Before proceeding remember to fully restart any open Terminal windows, and
|
||||||
|
their parent hosts like IDEs to reload any environmental variables, which
|
||||||
|
were altered during the installation process. This might be the quick fix.
|
||||||
|
|
||||||
|
=== ":material-apple: macOS"
|
||||||
|
|
||||||
|
On macOS the library lookup checks inside paths defined in [dyld][osx-dyld].
|
||||||
|
Additionally each library `name` is checked in [three variants][find-library-macOS]
|
||||||
|
with the `libname.dylib`, `name.dylib` and `name.framework/name` format.
|
||||||
|
|
||||||
|
[Homebrew] should set every needed variable to point at the installed
|
||||||
|
library directory, but if that didn't happen, you can use the debug script
|
||||||
|
below to see what paths are looked up.
|
||||||
|
|
||||||
|
A [known workaround][cffi-issue] is to add the Homebrew lib path directly
|
||||||
|
before running MkDocs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib
|
||||||
|
```
|
||||||
|
|
||||||
|
View source code of [cairo-lookup-macos.py]
|
||||||
|
|
||||||
|
```bash title="Python Debug macOS Script"
|
||||||
|
curl "https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/includes/debug/cairo-lookup-macos.py" | python -
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":fontawesome-brands-windows: Windows"
|
||||||
|
|
||||||
|
On Windows the library lookup checks inside the paths defined in the
|
||||||
|
environmental `PATH` variable. Additionally each library `name` is checked
|
||||||
|
in [two variants][find-library-Windows] with the `name` and `name.dll` format.
|
||||||
|
|
||||||
|
The default installation path of [GTK runtime] is:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
C:\Program Files\GTK3-Runtime Win64
|
||||||
|
```
|
||||||
|
|
||||||
|
and the libraries are in the `<INSTALL-DIR>\lib` directory. Use the debug
|
||||||
|
script below to check if the path is included. If it isn't then:
|
||||||
|
|
||||||
|
1. Press ++windows+r++.
|
||||||
|
2. Run the `SystemPropertiesAdvanced` applet.
|
||||||
|
3. Select "Environmental Variables" at the bottom.
|
||||||
|
4. Add the whole path to the `lib` directory to your `Path` variable.
|
||||||
|
5. Click OK on all open windows to apply changes.
|
||||||
|
6. Fully restart any open Terminal windows and their parent hosts like IDEs.
|
||||||
|
|
||||||
|
```powershell title="You can also list paths using PowerShell"
|
||||||
|
$env:Path -split ';'
|
||||||
|
```
|
||||||
|
|
||||||
|
View source code of [cairo-lookup-windows.py]
|
||||||
|
|
||||||
|
```powershell title="PowerShell - Python Debug Windows Script"
|
||||||
|
(Invoke-WebRequest "https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/includes/debug/cairo-lookup-windows.py").Content | python -
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-linux: Linux"
|
||||||
|
|
||||||
|
On Linux the library lookup can [differ greatly][find-library-Linux] and is
|
||||||
|
dependant from the installed distribution. For tested Ubuntu and Manjaro
|
||||||
|
systems Python runs shell commands to check which libraries are available in
|
||||||
|
[`ldconfig`][ubuntu-ldconfig], in the [`gcc`][ubuntu-gcc]/`cc` compiler, and
|
||||||
|
in [`ld`][ubuntu-ld].
|
||||||
|
|
||||||
|
You can extend the `LD_LIBRARY_PATH` environmental variable with an absolute
|
||||||
|
path to a library directory containing `libcairo.so` etc. Run this directly
|
||||||
|
before MkDocs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LD_LIBRARY_PATH=/absolute/path/to/lib:$LD_LIBRARY_PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also modify the `/etc/ld.so.conf` file.
|
||||||
|
|
||||||
|
The Python script below shows, which function is being run to find installed
|
||||||
|
libraries. You can check the source to find out what specific commands are
|
||||||
|
executed on your system during library lookup.
|
||||||
|
|
||||||
|
View source code of [cairo-lookup-linux.py]
|
||||||
|
|
||||||
|
```bash title="Python Debug Linux Script"
|
||||||
|
curl "https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/includes/debug/cairo-lookup-linux.py" | python -
|
||||||
|
```
|
||||||
|
|
||||||
|
[PyPi CairoSVG]: https://pypi.org/project/CairoSVG
|
||||||
|
[PyPi CairoCFFI]: https://pypi.org/project/CairoCFFI
|
||||||
|
[osx-dyld]: https://www.unix.com/man-page/osx/1/dyld/
|
||||||
|
[ubuntu-ldconfig]: https://manpages.ubuntu.com/manpages/focal/en/man8/ldconfig.8.html
|
||||||
|
[ubuntu-ld]: https://manpages.ubuntu.com/manpages/xenial/man1/ld.1.html
|
||||||
|
[ubuntu-gcc]: https://manpages.ubuntu.com/manpages/trusty/man1/gcc.1.html
|
||||||
|
[cffi-issue]: https://github.com/squidfunk/mkdocs-material/issues/5121
|
||||||
|
[cffi-dopen]: https://github.com/Kozea/cairocffi/blob/f1984d644bbc462ef0ec33b97782cf05733d7b53/cairocffi/__init__.py#L24-L49
|
||||||
|
[find-library-macOS]: https://github.com/python/cpython/blob/4d58a1d8fb27048c11bcbda3da1bebf78f979335/Lib/ctypes/util.py#L70-L81
|
||||||
|
[find-library-Windows]: https://github.com/python/cpython/blob/4d58a1d8fb27048c11bcbda3da1bebf78f979335/Lib/ctypes/util.py#L59-L67
|
||||||
|
[find-library-Linux]: https://github.com/python/cpython/blob/4d58a1d8fb27048c11bcbda3da1bebf78f979335/Lib/ctypes/util.py#L92
|
||||||
|
[cairo-lookup-macos.py]: https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/includes/debug/cairo-lookup-macos.py
|
||||||
|
[cairo-lookup-windows.py]: https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/includes/debug/cairo-lookup-windows.py
|
||||||
|
[cairo-lookup-linux.py]: https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/includes/debug/cairo-lookup-linux.py
|
||||||
|
111
includes/debug/cairo-lookup-linux.py
Normal file
111
includes/debug/cairo-lookup-linux.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from ctypes import util
|
||||||
|
|
||||||
|
|
||||||
|
class CustomPopen(subprocess.Popen):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
print(f"Subprocess command:\n {' '.join(args[0])}")
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def communicate(self, *args, **kwargs):
|
||||||
|
out, _ = super().communicate(*args, **kwargs)
|
||||||
|
out = out.rstrip()
|
||||||
|
print("Subprocess output:")
|
||||||
|
if out:
|
||||||
|
print(f" {os.fsdecode(out)}")
|
||||||
|
else:
|
||||||
|
print(f" Output is empty")
|
||||||
|
return out, _
|
||||||
|
|
||||||
|
def __getattribute__(self, name_):
|
||||||
|
att = super().__getattribute__(name_)
|
||||||
|
if name_ == "stdout" and att is not None:
|
||||||
|
att.read = self.read_wrapper(att.read)
|
||||||
|
return att
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_wrapper(func):
|
||||||
|
|
||||||
|
if func.__name__ == "wrapper":
|
||||||
|
return func
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
output = func(*args, **kwargs)
|
||||||
|
print("Subprocess output:")
|
||||||
|
for line_ in os.fsdecode(output).split("\n"):
|
||||||
|
print(line_)
|
||||||
|
return output
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
subprocess.Popen = CustomPopen
|
||||||
|
|
||||||
|
print("ctypes.util script with the find_library:")
|
||||||
|
print(inspect.getsourcefile(util.find_library), end="\n\n")
|
||||||
|
|
||||||
|
print("find_library function:")
|
||||||
|
func_lines = list(map(str.rstrip, inspect.getsourcelines(util.find_library)[0]))
|
||||||
|
indent = len(func_lines[0]) - len(func_lines[0].lstrip())
|
||||||
|
for line in func_lines:
|
||||||
|
print(line.replace(" " * indent, "", 1))
|
||||||
|
|
||||||
|
library_names = ("cairo-2", "cairo", "libcairo-2")
|
||||||
|
filenames = ("libcairo.so.2", "libcairo.2.dylib", "libcairo-2.dll")
|
||||||
|
c_compiler = shutil.which("gcc") or shutil.which("cc")
|
||||||
|
ld_env = os.environ.get("LD_LIBRARY_PATH")
|
||||||
|
first_found = ""
|
||||||
|
|
||||||
|
print("\nLD_LIBRARY_PATH =", ld_env, end="\n\n")
|
||||||
|
|
||||||
|
for name in library_names:
|
||||||
|
if hasattr(util, "_findSoname_ldconfig"):
|
||||||
|
result = util._findSoname_ldconfig(name)
|
||||||
|
print(f"_findSoname_ldconfig({name}) ->", result)
|
||||||
|
if result:
|
||||||
|
print(f"Found {result}")
|
||||||
|
if not first_found:
|
||||||
|
first_found = result
|
||||||
|
print("---")
|
||||||
|
if c_compiler and hasattr(util, "_findLib_gcc"):
|
||||||
|
result = util._findLib_gcc(name)
|
||||||
|
print(f"_findLib_gcc({name}) ->", result)
|
||||||
|
if result and hasattr(util, "_get_soname"):
|
||||||
|
result = util._get_soname(result)
|
||||||
|
if result:
|
||||||
|
print(f"Found {result}")
|
||||||
|
if not first_found:
|
||||||
|
first_found = result
|
||||||
|
print("---")
|
||||||
|
if hasattr(util, "_findLib_ld"):
|
||||||
|
result = util._findLib_ld(name)
|
||||||
|
print(f"_findLib_ld({name}) ->", result)
|
||||||
|
if result and hasattr(util, "_get_soname"):
|
||||||
|
result = util._get_soname(result)
|
||||||
|
if result:
|
||||||
|
print(f"Found {result}")
|
||||||
|
if not first_found:
|
||||||
|
first_found = result
|
||||||
|
print("---")
|
||||||
|
if hasattr(util, "_findLib_crle"):
|
||||||
|
result = util._findLib_crle(name, False)
|
||||||
|
print(f"_findLib_crle({name}) ->", result)
|
||||||
|
if result and hasattr(util, "_get_soname"):
|
||||||
|
result = util._get_soname(result)
|
||||||
|
if result:
|
||||||
|
print(f"Found {result}")
|
||||||
|
if not first_found:
|
||||||
|
first_found = result
|
||||||
|
print("---")
|
||||||
|
|
||||||
|
if first_found:
|
||||||
|
filenames = (first_found,) + filenames
|
||||||
|
|
||||||
|
print(f"The path is {first_found or 'not found'}")
|
||||||
|
print("List of files that FFI will try to load:")
|
||||||
|
for filename in filenames:
|
||||||
|
print("-", filename)
|
49
includes/debug/cairo-lookup-macos.py
Normal file
49
includes/debug/cairo-lookup-macos.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import os
|
||||||
|
from ctypes.macholib import dyld
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
library_names = ("cairo-2", "cairo", "libcairo-2")
|
||||||
|
filenames = ("libcairo.so.2", "libcairo.2.dylib", "libcairo-2.dll")
|
||||||
|
first_found = ""
|
||||||
|
names = []
|
||||||
|
|
||||||
|
for name in library_names:
|
||||||
|
names += [
|
||||||
|
"lib%s.dylib" % name,
|
||||||
|
"%s.dylib" % name,
|
||||||
|
"%s.framework/%s" % (name, name),
|
||||||
|
]
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
for path in dyld.dyld_image_suffix_search(
|
||||||
|
chain(
|
||||||
|
dyld.dyld_override_search(name),
|
||||||
|
dyld.dyld_executable_path_search(name),
|
||||||
|
dyld.dyld_default_search(name),
|
||||||
|
)
|
||||||
|
):
|
||||||
|
if os.path.isfile(path):
|
||||||
|
print(f"Found: {path}")
|
||||||
|
if not first_found:
|
||||||
|
first_found = path
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
if dyld._dyld_shared_cache_contains_path(path):
|
||||||
|
print(f"Found: {path}")
|
||||||
|
if not first_found:
|
||||||
|
first_found = path
|
||||||
|
continue
|
||||||
|
except NotImplementedError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"Doesn't exist: {path}")
|
||||||
|
print("---")
|
||||||
|
|
||||||
|
if first_found:
|
||||||
|
filenames = (first_found,) + filenames
|
||||||
|
|
||||||
|
print(f"The path is {first_found or 'not found'}")
|
||||||
|
print("List of files that FFI will try to load:")
|
||||||
|
for filename in filenames:
|
||||||
|
print("-", filename)
|
31
includes/debug/cairo-lookup-windows.py
Normal file
31
includes/debug/cairo-lookup-windows.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
library_names = ("cairo-2", "cairo", "libcairo-2")
|
||||||
|
filenames = ("libcairo.so.2", "libcairo.2.dylib", "libcairo-2.dll")
|
||||||
|
first_found = ""
|
||||||
|
names = []
|
||||||
|
|
||||||
|
for name in library_names:
|
||||||
|
if name.lower().endswith(".dll"):
|
||||||
|
names += [name]
|
||||||
|
else:
|
||||||
|
names += [name, name + ".dll"]
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
for path in os.environ["PATH"].split(os.pathsep):
|
||||||
|
resolved_path = os.path.join(path, name)
|
||||||
|
if os.path.exists(resolved_path):
|
||||||
|
print(f"Found: {resolved_path}")
|
||||||
|
if not first_found:
|
||||||
|
first_found = resolved_path
|
||||||
|
continue
|
||||||
|
print(f"Doesn't exist: {resolved_path}")
|
||||||
|
print("---")
|
||||||
|
|
||||||
|
if first_found:
|
||||||
|
filenames = (first_found,) + filenames
|
||||||
|
|
||||||
|
print(f"The path is {first_found or 'not found'}")
|
||||||
|
print("List of files that FFI will try to load:")
|
||||||
|
for filename in filenames:
|
||||||
|
print("-", filename)
|
@ -49,14 +49,25 @@ from mkdocs.plugins import BasePlugin
|
|||||||
from mkdocs.utils import copy_file
|
from mkdocs.utils import copy_file
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
try:
|
|
||||||
from cairosvg import svg2png
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from .config import SocialConfig
|
from .config import SocialConfig
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
except ImportError as e:
|
||||||
|
import_errors = {repr(e)}
|
||||||
|
else:
|
||||||
|
import_errors = set()
|
||||||
|
|
||||||
|
cairosvg_error: str = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cairosvg import svg2png
|
||||||
|
except ImportError as e:
|
||||||
|
import_errors.add(repr(e))
|
||||||
|
except OSError as e:
|
||||||
|
cairosvg_error = str(e)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Classes
|
# Classes
|
||||||
@ -76,10 +87,18 @@ class SocialPlugin(BasePlugin[SocialConfig]):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Check dependencies
|
# Check dependencies
|
||||||
if "Image" not in globals():
|
if import_errors:
|
||||||
raise PluginError(
|
raise PluginError(
|
||||||
"Required dependencies of \"social\" plugin not found. "
|
"Required dependencies of \"social\" plugin not found:\n"
|
||||||
"Install with: pip install \"mkdocs-material[imaging]\""
|
+ str("\n".join(map(lambda x: "- " + x, import_errors)))
|
||||||
|
+ "\n\n--> Install with: pip install \"mkdocs-material[imaging]\""
|
||||||
|
)
|
||||||
|
|
||||||
|
if cairosvg_error:
|
||||||
|
raise PluginError(
|
||||||
|
"\"cairosvg\" Python module is installed, but it crashed with:\n"
|
||||||
|
+ cairosvg_error
|
||||||
|
+ "\n\n--> Check out the troubleshooting guide: https://t.ly/MfX6u"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Move color options
|
# Move color options
|
||||||
|
@ -49,14 +49,25 @@ from mkdocs.plugins import BasePlugin
|
|||||||
from mkdocs.utils import copy_file
|
from mkdocs.utils import copy_file
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
try:
|
|
||||||
from cairosvg import svg2png
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from .config import SocialConfig
|
from .config import SocialConfig
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
except ImportError as e:
|
||||||
|
import_errors = {repr(e)}
|
||||||
|
else:
|
||||||
|
import_errors = set()
|
||||||
|
|
||||||
|
cairosvg_error: str = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cairosvg import svg2png
|
||||||
|
except ImportError as e:
|
||||||
|
import_errors.add(repr(e))
|
||||||
|
except OSError as e:
|
||||||
|
cairosvg_error = str(e)
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Classes
|
# Classes
|
||||||
@ -76,10 +87,18 @@ class SocialPlugin(BasePlugin[SocialConfig]):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Check dependencies
|
# Check dependencies
|
||||||
if "Image" not in globals():
|
if import_errors:
|
||||||
raise PluginError(
|
raise PluginError(
|
||||||
"Required dependencies of \"social\" plugin not found. "
|
"Required dependencies of \"social\" plugin not found:\n"
|
||||||
"Install with: pip install \"mkdocs-material[imaging]\""
|
+ str("\n".join(map(lambda x: "- " + x, import_errors)))
|
||||||
|
+ "\n\n--> Install with: pip install \"mkdocs-material[imaging]\""
|
||||||
|
)
|
||||||
|
|
||||||
|
if cairosvg_error:
|
||||||
|
raise PluginError(
|
||||||
|
"\"cairosvg\" Python module is installed, but it crashed with:\n"
|
||||||
|
+ cairosvg_error
|
||||||
|
+ "\n\n--> Check out the troubleshooting guide: https://t.ly/MfX6u"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Move color options
|
# Move color options
|
||||||
|
Loading…
Reference in New Issue
Block a user