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
135 lines
4.2 KiB
Python
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()
|