Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
764c73a
test package import python
cedrik-fuoco-adsk Jan 12, 2026
84e4961
forgot to add test_python_imports.py
cedrik-fuoco-adsk Jan 12, 2026
a249f3d
remove build dependencies
cedrik-fuoco-adsk Jan 12, 2026
fbb62e4
autogenerate the module list to test from requirement.txt.in
cedrik-fuoco-adsk Jan 12, 2026
9a83dc0
Merge branch 'main' into add-python-test-import
cedrik-fuoco-adsk Jan 12, 2026
79a851e
fix windows
cedrik-fuoco-adsk Jan 12, 2026
ed522ea
POC - Test OTIO fix in windows debug
cedrik-fuoco-adsk Jan 13, 2026
c2c183f
POC - Test OTIO fix in windows debug
cedrik-fuoco-adsk Jan 13, 2026
c3029c5
Merge branch 'main' into add-python-test-import
cedrik-fuoco-adsk Jan 13, 2026
38871ae
try to fix debug
cedrik-fuoco-adsk Jan 15, 2026
0fcf46b
fix otio with patch for python debug
cedrik-fuoco-adsk Jan 16, 2026
395d5dc
patch OTIO for now
cedrik-fuoco-adsk Jan 16, 2026
1826cf1
tweak, clean up
cedrik-fuoco-adsk Jan 16, 2026
5f49c77
try to fix issue seen in CI only
cedrik-fuoco-adsk Jan 16, 2026
799f732
use fork for now, until fix in OTIO repo?
cedrik-fuoco-adsk Jan 16, 2026
4612a92
fix import script for url format
cedrik-fuoco-adsk Jan 16, 2026
fc625d9
undo some changes
cedrik-fuoco-adsk Jan 16, 2026
5e698c9
clean up, no need to add PATHs to PATH env.var
cedrik-fuoco-adsk Jan 16, 2026
42a1236
Merge branch 'main' into add-python-test-import
cedrik-fuoco-adsk Jan 27, 2026
f89d02b
Back to official repo
cedrik-fuoco-adsk Mar 26, 2026
8ad3420
Merge branch 'main' into add-python-test-import
cedrik-fuoco-adsk Mar 26, 2026
0ac10b5
Build from a known commit that fixes issues with windows debug
cedrik-fuoco-adsk Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions cmake/dependencies/python3.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,6 @@ SET(_python3_download_hash
"${RV_DEPS_PYTHON_DOWNLOAD_HASH}"
)

SET(_opentimelineio_download_url
"https://github.com/AcademySoftwareFoundation/OpenTimelineIO"
)

SET(_opentimelineio_git_tag
"v${_opentimelineio_version}"
)

SET(_pyside_archive_url
"${RV_DEPS_PYSIDE_ARCHIVE_URL}"
)
Expand Down Expand Up @@ -433,21 +425,40 @@ ADD_CUSTOM_COMMAND(
DEPENDS ${_python3_target} ${${_python3_target}-build-deps-flag} ${_requirements_output_file} ${_requirements_input_file}
)

# Test the Python distribution after requirements are installed
# Test Python package imports after requirements are installed. This validates that all pip-installed packages (numpy, opentimelineio, OpenGL, cryptography,
# etc.) can be imported successfully and tests for ABI compatibility issues. Runs in-place using the built Python executable.
SET(${_python3_target}-imports-test-flag
${_install_dir}/${_python3_target}-imports-test-flag
)

SET(_test_python_imports_script
"${PROJECT_SOURCE_DIR}/src/build/test_python_imports.py"
)

ADD_CUSTOM_COMMAND(
COMMENT "Testing Python package imports (build-time validation)"
OUTPUT ${${_python3_target}-imports-test-flag}
COMMAND "${_python3_executable}" "${_test_python_imports_script}"
COMMAND cmake -E touch ${${_python3_target}-imports-test-flag}
DEPENDS ${${_python3_target}-requirements-flag} ${_test_python_imports_script}
)

# Test the Python distribution's relocatability and environment setup. This moves Python to a temporary location to ensure it works when relocated and validates
# SSL_CERT_FILE setup via sitecustomize.py. Uses system Python to orchestrate the test (since the built Python gets moved during testing).
SET(${_python3_target}-test-flag
${_install_dir}/${_python3_target}-test-flag
)

SET(_test_python_script
"${PROJECT_SOURCE_DIR}/src/build/test_python.py"
"${PROJECT_SOURCE_DIR}/src/build/test_python_distribution.py"
)

ADD_CUSTOM_COMMAND(
COMMENT "Testing Python distribution"
COMMENT "Testing Python distribution relocatability and environment"
OUTPUT ${${_python3_target}-test-flag}
COMMAND python3 "${_test_python_script}" --python-home "${_install_dir}" --variant "${CMAKE_BUILD_TYPE}"
COMMAND cmake -E touch ${${_python3_target}-test-flag}
DEPENDS ${${_python3_target}-requirements-flag} ${_test_python_script}
DEPENDS ${${_python3_target}-imports-test-flag} ${_test_python_script}
)

IF(RV_TARGET_WINDOWS
Expand Down
3 changes: 2 additions & 1 deletion src/build/requirements.txt.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ pip==24.0 # License: MIT License (MIT)
setuptools==69.5.1 # License: MIT License
wheel==0.43.0 # License: MIT License (MIT)
numpy==@_numpy_version@ # License: BSD License (BSD-3-Clause) - Required by PySide6
opentimelineio==@_opentimelineio_version@ # License: Other/Proprietary License (Modified Apache 2.0 License)
# This commit fixes issue with OTIO and the Windows debug build.
opentimelineio @ git+https://github.com/AcademySoftwareFoundation/OpenTimelineIO.git@d793d6f99729ffc5d3ed4943ea17e8fd6065f5ae # License: Other/Proprietary License (Modified Apache 2.0 License)
PyOpenGL==3.1.7 # License: BSD License (BSD)
PyOpenGL_accelerate==3.1.10 # License: BSD License (BSD) - v3.1.10 includes Python 3 fix for Cython compatibility

Expand Down
File renamed without changes.
164 changes: 164 additions & 0 deletions src/build/test_python_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# *****************************************************************************
# Copyright 2025 Autodesk, Inc. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
# *****************************************************************************

"""
Test script to validate all Python package imports at build time.
This catches issues like missing dependencies, ABI incompatibilities, and
configuration problems (like OpenSSL legacy provider) before running the build.

Package list is automatically generated from requirements.txt.in to prevent
manual synchronization errors.
"""

import os
import re
import sys

# Packages to skip from requirements.txt.in (e.g., build dependencies that don't need import testing).
SKIP_IMPORTS = [
"pip",
"setuptools",
"wheel",
]

# Additional imports to test beyond what's in requirements.txt.in.
# These test deeper functionality (e.g., sub-modules that verify OpenSSL legacy provider support).
ADDITIONAL_IMPORTS = [
"cryptography.fernet",
"cryptography.hazmat.bindings._rust",
]


def parse_requirements(file_path):
"""Parse requirements.txt.in and return list of package names.

Extracts package names from lines like:
- numpy==@_numpy_version@
- pip==24.0

Skips comments and empty lines.
"""
packages = []
with open(file_path, encoding="utf-8") as f:
for line in f:
# Remove inline comments and whitespace
line = line.split("#", 1)[0].strip()
# Skip comments and empty lines
if not line or line.startswith("#"):
continue
# Handle VCS/URL form: name @ git+...
if " @ " in line:
packages.append(line.split(" @ ", 1)[0].strip())
continue
# Extract package name (before ==, <, >, etc.)
match = re.match(r"^([A-Za-z0-9_-]+)", line)
if match:
packages.append(match.group(1))
return packages


def try_import(package_name):
"""Try importing package, with fallback to stripped 'Py' prefix.

Handles cases where pip package name differs from import name:
- PyOpenGL -> OpenGL
- PyOpenGL_accelerate -> OpenGL_accelerate

Raises ImportError if both attempts fail.
"""
try:
return __import__(package_name)
except ImportError:
if package_name.startswith("Py"):
# Try without "Py" prefix
return __import__(package_name[2:])
raise


def test_imports():
"""Test that all required packages can be imported."""
# No PATH manipulation needed.

# Get requirements.txt.in from same directory as this script
script_dir = os.path.dirname(os.path.abspath(__file__))
requirements_file = os.path.join(script_dir, "requirements.txt.in")

if not os.path.exists(requirements_file):
print(f"ERROR: Could not find {requirements_file}")
return 1

# Parse package list from requirements.txt.in
packages = parse_requirements(requirements_file)

# Build list of imports to test (packages from requirements.txt + additional imports)
imports_to_test = []
skipped_packages = []
for package in packages:
# Skip build dependencies and other packages that don't need import testing
if package not in SKIP_IMPORTS:
imports_to_test.append((package, "from requirements.txt"))
else:
skipped_packages.append(package)

# Add any additional imports (e.g., sub-modules for deeper testing)
for additional_import in ADDITIONAL_IMPORTS:
imports_to_test.append((additional_import, "additional import"))

failed_imports = []
successful_imports = []

print("=" * 80)
print("Testing Python package imports at build time")
print(f"Python: {sys.version}")
print(f"Platform: {sys.platform}")
print(f"Executable: {sys.executable}")
print(f"Source: {requirements_file}")
if skipped_packages:
print(f"Skipping: {', '.join(skipped_packages)}")

print("=" * 80)
print()

for module_name, description in imports_to_test:
try:
print(f"Testing {module_name:45} ({description})...", end=" ")
try_import(module_name)
print("OK")
successful_imports.append(module_name)
except Exception as e:
print("FAILED")
print(f" Error: {type(e).__name__}: {e}")
failed_imports.append((module_name, e))

# Keep failure output minimal; detailed diagnostics removed.

print()
print("=" * 80)
print(f"Results: {len(successful_imports)} passed, {len(failed_imports)} failed")
print("=" * 80)

if failed_imports:
print()
print("FAILED IMPORTS:")
for module_name, error in failed_imports:
print(f" - {module_name}: {type(error).__name__}: {error}")
print()
print("Build-time import test FAILED!")
print("One or more required Python packages could not be imported.")
print()
return 1
else:
print()
print("All Python package imports successful!")
return 0


if __name__ == "__main__":
sys.exit(test_imports())
Loading