-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathinstall_dependencies.py
125 lines (99 loc) · 5.59 KB
/
install_dependencies.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
'''
Copyright (C) 2021-2022 Orange Turbine
https://orangeturbine.com
orangeturbine@cgcookie.com
Originally created in install-dependencies by Robert Guetzkow (https://github.com/robertguetzkow/blender-python-examples/)
Modified by Jason van Gumster
This file is part of Swiffle.
Swiffle is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <https://www.gnu.org/licenses/>.
'''
import bpy
import os
import sys
import subprocess
import importlib
from collections import namedtuple
# Local imports
from . import global_vars
Dependency = namedtuple("Dependency", ["module", "package", "name"])
# Declare all modules that this add-on depends on, that may need to be installed. The package and (global) name can be
# set to None, if they are equal to the module name. See import_module and ensure_and_import_module for the explanation
# of the arguments. DO NOT use this to import other parts of your Python add-on, import them as usual with an
# "import" statement.
dependencies = (
Dependency(module="PIL", package="pillow", name="pillow"),
Dependency(module="lxml", package=None, name=None),
Dependency(module="pylzma", package=None, name=None)
)
def import_module(module_name, global_name=None, reload=True):
"""
Import a module.
:param module_name: Module to import.
:param global_name: (Optional) Name under which the module is imported. If None the module_name will be used.
This allows to import under a different name with the same effect as e.g. "import numpy as np" where "np" is
the global_name under which the module can be accessed.
:raises: ImportError and ModuleNotFoundError
"""
if global_name is None:
global_name = module_name
if global_name in globals():
importlib.reload(globals()[global_name])
else:
# Attempt to import the module and assign it to globals dictionary. This allow to access the module under
# the given name, just like the regular import would.
globals()[global_name] = importlib.import_module(module_name)
def install_pip():
"""
Installs pip if not already present. Please note that ensurepip.bootstrap() also calls pip, which adds the
environment variable PIP_REQ_TRACKER. After ensurepip.bootstrap() finishes execution, the directory doesn't exist
anymore. However, when subprocess is used to call pip, in order to install a package, the environment variables
still contain PIP_REQ_TRACKER with the now nonexistent path. This is a problem since pip checks if PIP_REQ_TRACKER
is set and if it is, attempts to use it as temp directory. This would result in an error because the
directory can't be found. Therefore, PIP_REQ_TRACKER needs to be removed from environment variables.
:return:
"""
try:
# Check if pip is already installed
subprocess.run([sys.executable, "-m", "pip", "--version"], check=True)
except subprocess.CalledProcessError:
import ensurepip
ensurepip.bootstrap()
os.environ.pop("PIP_REQ_TRACKER", None)
def install_and_import_module(module_name, package_name=None, global_name=None):
"""
Installs the package through pip and attempts to import the installed module.
:param module_name: Module to import.
:param package_name: (Optional) Name of the package that needs to be installed. If None it is assumed to be equal
to the module_name.
:param global_name: (Optional) Name under which the module is imported. If None the module_name will be used.
This allows to import under a different name with the same effect as e.g. "import numpy as np" where "np" is
the global_name under which the module can be accessed.
:raises: subprocess.CalledProcessError and ImportError
"""
if package_name is None:
package_name = module_name
if global_name is None:
global_name = module_name
# Blender disables the loading of user site-packages by default. However, pip will still check them to determine
# if a dependency is already installed. This can cause problems if the packages is installed in the user
# site-packages and pip deems the requirement satisfied, but Blender cannot import the package from the user
# site-packages. Hence, the environment variable PYTHONNOUSERSITE is set to disallow pip from checking the user
# site-packages. If the package is not already installed for Blender's Python interpreter, it will then try to.
# The paths used by pip can be checked with `subprocess.run([bpy.app.binary_path_python, "-m", "site"], check=True)`
# Create a copy of the environment variables and modify them for the subprocess call
environ_copy = dict(os.environ)
environ_copy["PYTHONNOUSERSITE"] = "1"
#XXX: Note to self, if running this in Arch Linux, you may need to prepend 'sudo' at the start of the subprocess
# since Blender in Arch uses the system Python
subprocess.run([sys.executable, "-m", "pip", "install", package_name], check=True, env=environ_copy)
# The installation succeeded, attempt to import the module again
import_module(module_name, global_name)