Files
test2/tests/python/system_python/load_tool_scripts.py
Bastien Montagne 2c9ab53273 Add 'system python' validation for some py scripts.
The goal of this test is to try to import some critical py scripts with the
system python of the building machine.
The main target is to ensure that these py scripts remain usable by all
buildbot machines, as some of them are using fairly outdated python
versions.

Current status:
* Scripts in `build_files` and `docs` are checked.
* Some python scripts in `build_files` were 'reverted' to be compatible
  with older required python version currently (3.6).
* A few scripts are excluded from the test, mostly because they use Blender's
  `bpy` module, which means they are only intended to be ran with Blender's
  python anyway.
* The test is only enabled for Linux buildbots currently, as they use the
  oldest Python by far.

Notes:
* Some more scripts are likely to be moved around in the future.
* Whether these tests need to be enabled on windows or macos platforms remains
  an open question.

Pull Request: https://projects.blender.org/blender/blender/pulls/130746
2024-12-24 11:55:29 +01:00

135 lines
4.2 KiB
Python

# SPDX-FileCopyrightText: 2024 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import sys
import pathlib
import importlib.util
import contextlib
import traceback
# For some reasons, these two modules do not work with 'file-path-based' import method used by this script.
# So 'pre-load' them here instead.
# They are _not used_ directly by this script.
import multiprocessing
import typing
# Allow-list of directories, relative to the given `--root-dir` argument.
INCLUDED_DIRS = {
"build_files",
# Used to generate the manual and API documentations.
"doc",
}
# Block-list of paths to python modules, relative to the given `--root-dir` argument.
EXCLUDED_FILE_PATHS = {
# Require `bpy` module.
"doc/python_api/sphinx_doc_gen.py",
# XXX These scripts execute on import! bad, need to be fixed or removed.
# FIXME: Should be reasonably trivial to fix/cleanup for most of them.
"doc/python_api/conf.py",
}
@contextlib.contextmanager
def add_to_sys_path(syspath):
old_path = sys.path
old_modules = sys.modules
sys.modules = old_modules.copy()
sys.path = sys.path[:]
sys.path.insert(0, str(syspath))
try:
yield
finally:
sys.path = old_path
sys.modules = old_modules
def import_module(file_path):
"""Returns `...` if not a python module, `True` if it's a package, `False` otherwise."""
file_path = pathlib.Path(file_path)
if file_path.suffix != ".py":
return ...
file_name = file_path.name
if not file_name:
return ...
is_package = file_name == "__init__.py"
if is_package:
assert (len(file_path.parts) >= 2)
module_name = file_path.parts[-2]
else:
module_name = file_path.stem
with add_to_sys_path(file_path.parent):
spec = importlib.util.spec_from_file_location(
module_name, file_path, submodule_search_locations=file_path.parent)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module_name, is_package
def import_modules(root_dir):
print("+++", sys.executable)
included_directories = [os.path.join(root_dir, p) for p in INCLUDED_DIRS]
excluded_file_paths = {os.path.join(root_dir, p) for p in EXCLUDED_FILE_PATHS}
has_failures = False
directories = included_directories[:]
while directories:
path = directories.pop(0)
sub_directories = []
is_package = False
with os.scandir(path) as it:
for entry in it:
if entry.is_dir():
if entry.name.startswith('.'):
continue
sub_directories.append(entry.path)
continue
if not entry.is_file():
continue
if not entry.name.endswith(".py"):
continue
if entry.path in excluded_file_paths:
continue
try:
is_current_package = import_module(entry.path)
is_package = is_package or is_current_package
except SystemExit as e:
has_failures = True
print(f"+++ Failed to import {entry.path} (module called `sys.exit`), {e}")
except Exception as e:
has_failures = True
print(f"+++ Failed to import {entry.path} ({e.__class__}), {e}")
traceback.print_tb(e.__traceback__)
print("\n\n")
# Do not attempt to import individual modules of a package. For now assume that if the top-level package can
# be imported, it is good enough. This may have to be revisited at some point though. Currently there are
# no packages in target directories anyway.
if not is_package:
directories += sub_directories
if has_failures:
raise Exception("Some module imports failed")
def main():
import sys
if sys.argv[1] == "--root-dir":
root_dir = sys.argv[2]
import_modules(root_dir=root_dir)
else:
raise Exception("Missing --root-dir parameter")
if __name__ == "__main__":
main()