From fbcbd3249be28382632df91a0a3ae3e30e0c3794 Mon Sep 17 00:00:00 2001 From: "Sam.Richards@taurich.org" Date: Sun, 25 Jan 2026 15:06:04 +0000 Subject: [PATCH 1/6] PySide6 fixes. Signed-off-by: Sam.Richards@taurich.org --- src/CMakeLists.txt | 11 +++-- src/launch/xstudio/src/CMakeLists.txt | 22 +++++++++- src/pyside6_module/src/CMakeLists.txt | 62 +++++++++++++++++++++------ vcpkg.json | 13 ++++-- 4 files changed, 88 insertions(+), 20 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f777da9da..70a5aedca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,11 +76,16 @@ endif () if (BUILD_PYSIDE_WIDGETS) cmake_policy(SET CMP0148 OLD) # shiboken uses deprecated FindPythonInterp - find_package(Shiboken6 QUIET) - find_package(PySide6 QUIET) + + # We build PySide6 locally to ensure version compatibility + include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/BuildPySide6.cmake") + + # Manually set FOUND variables so downstream logic works + set(Shiboken6_FOUND TRUE) + set(PySide6_FOUND TRUE) if (INSTALL_PYTHON_MODULE AND Shiboken6_FOUND AND PySide6_FOUND) - message("PySide6 and Shiboken6 packages found. Adding PySide6 demo to targets!") + message("PySide6 and Shiboken6 are being built locally. Adding PySide6 demo to targets!") add_src_and_test_always(pyside6_module) add_src_and_test_always(demos/pyside_demo) else() diff --git a/src/launch/xstudio/src/CMakeLists.txt b/src/launch/xstudio/src/CMakeLists.txt index 23a659d34..55b8bcccc 100644 --- a/src/launch/xstudio/src/CMakeLists.txt +++ b/src/launch/xstudio/src/CMakeLists.txt @@ -140,6 +140,9 @@ elseif(APPLE) COMMAND ${CMAKE_COMMAND} -E copy ${FFMPEG_APP} "${CMAKE_BINARY_DIR}/xSTUDIO.app/Contents/MacOS/ffmpeg") + # Install PySide6 and Shiboken6 into the app bundle for local execution + # PySide6 installation moved to end of file to ensure it runs after macdeployqt + get_target_property(_qmake_executable Qt6::qmake IMPORTED_LOCATION) get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) find_program(macdeployqt_exe macdeployqt HINTS "${_qt_bin_dir}") @@ -169,9 +172,26 @@ else() DESTINATION bin RENAME xstudio) - install(PROGRAMS + install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/xstudio_desktop_integration.sh DESTINATION bin RENAME xstudio_desktop_integration) +endif() + +# Install PySide6 and Shiboken6 into the app bundle for local execution (Apple only for now in this block) +if(APPLE) + # Construct path manually as target property might be empty or config-specific + set(_pyside6_site_packages "${CMAKE_BINARY_DIR}/python/lib/python3.11/site-packages") + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/xSTUDIO.app/Contents/Resources/python/lib" + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${_pyside6_site_packages}/PySide6" + "${CMAKE_BINARY_DIR}/xSTUDIO.app/Contents/Resources/python/lib/PySide6" + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${_pyside6_site_packages}/shiboken6" + "${CMAKE_BINARY_DIR}/xSTUDIO.app/Contents/Resources/python/lib/shiboken6" + # Sign all copied binaries to prevent SIGKILL on Apple Silicon + COMMAND find "${CMAKE_BINARY_DIR}/xSTUDIO.app/Contents/Resources/python/lib" -name "*.so" -o -name "*.dylib" | xargs codesign --force -s - + ) endif() \ No newline at end of file diff --git a/src/pyside6_module/src/CMakeLists.txt b/src/pyside6_module/src/CMakeLists.txt index f0b9b5a4f..c94ab1e73 100644 --- a/src/pyside6_module/src/CMakeLists.txt +++ b/src/pyside6_module/src/CMakeLists.txt @@ -9,8 +9,8 @@ if (CLANG_PATH) set(ENV{CLANG_INSTALL_DIR} $CLANG_PATH) endif() -find_package(Shiboken6 REQUIRED) -find_package(PySide6 REQUIRED) +# find_package(Shiboken6 REQUIRED) +# find_package(PySide6 REQUIRED) find_package(Python COMPONENTS Interpreter Development) get_target_property(PySide6_INCLUDE_DIR PySide6::pyside6 INTERFACE_INCLUDE_DIRECTORIES) @@ -47,14 +47,46 @@ set(QT_INCLUDE_DIRS ${QT_CORE_INCLUDE_DIRS} ${QT_GUI_INCLUDE_DIRS} ${QT_WIDGETS_ ${QT_QUICKWIDGETS_INCLUDE_DIRS} ${QT_QUICK_INCLUDE_DIRS} ${QT_NETWORK_INCLUDE_DIRS} ${QT_QML_INCLUDE_DIRS} ${QT_OPENGL_INCLUDE_DIRS} ${QT_OPNGLWIDGETS_INCLUDE_DIRS}) set(INCLUDES "") + foreach(INCLUDE_DIR ${QT_INCLUDE_DIRS}) list(APPEND INCLUDES "-I${INCLUDE_DIR}") endforeach() -# hack alert. Adding this is the only way I can get the 'clang_parseTranslationUnit2' step -# to complete without failing with this --- fatal error: 'stddef.h' file not found -# Unlikely to work on systems other than Rock8.9!!! -list(APPEND INCLUDES "-I/opt/rh/gcc-toolset-11/root/usr/lib/gcc/x86_64-redhat-linux/11/include") +if (APPLE) + # create a flat include tree to make shiboken happy with includes + set(SHIBOKEN_FIX_DIR "${CMAKE_CURRENT_BINARY_DIR}/shiboken_include_fix") + file(MAKE_DIRECTORY ${SHIBOKEN_FIX_DIR}) + list(APPEND INCLUDES "-I${SHIBOKEN_FIX_DIR}") + foreach(INCLUDE_DIR ${QT_INCLUDE_DIRS}) + # INCLUDE_DIR is .../Name.framework/Headers usually + get_filename_component(FRAMEWORK_DIR "${INCLUDE_DIR}/.." ABSOLUTE) + get_filename_component(FRAMEWORK_NAME_EXT "${FRAMEWORK_DIR}" NAME) # Name.framework + string(REPLACE ".framework" "" MODULE_NAME "${FRAMEWORK_NAME_EXT}") + + if ("${FRAMEWORK_NAME_EXT}" MATCHES "\\.framework$") + set(TARGET_DIR "${SHIBOKEN_FIX_DIR}/${MODULE_NAME}") + if (NOT EXISTS "${TARGET_DIR}") + execute_process(COMMAND ln -s "${INCLUDE_DIR}" "${TARGET_DIR}") + endif() + endif() + endforeach() +endif() +if (APPLE) + get_target_property(QT_CORE_LIB Qt6::Core LOCATION) + # QT_CORE_LIB is .../lib/QtCore.framework/Versions/A/QtCore + # We need .../lib + get_filename_component(QT_LIB_DIR ${QT_CORE_LIB} DIRECTORY) + get_filename_component(QT_LIB_DIR ${QT_LIB_DIR} DIRECTORY) + get_filename_component(QT_LIB_DIR ${QT_LIB_DIR} DIRECTORY) + get_filename_component(QT_LIB_DIR ${QT_LIB_DIR} DIRECTORY) + # Add clang resource path for stddef.h etc + list(APPEND INCLUDES "-I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/17/include") + list(APPEND INCLUDES "-F${QT_LIB_DIR}") + list(APPEND INCLUDES "-I${QT_LIB_DIR}/../include") + list(APPEND INCLUDES "--clang-options=-isysroot,/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk") +endif() + + set(SHIBOKEN_OPTIONS --generator-set=shiboken --enable-parent-ctor-heuristic --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero @@ -62,7 +94,7 @@ set(SHIBOKEN_OPTIONS --generator-set=shiboken --enable-parent-ctor-heuristic ${INCLUDES} -I${CMAKE_CURRENT_SOURCE_DIR} -T${CMAKE_CURRENT_SOURCE_DIR} - -T${PySide6_INCLUDE_DIR}/../../share/PySide6/typesystems + -T${PySide6_INCLUDE_DIR}/../typesystems --output-directory=${CMAKE_CURRENT_BINARY_DIR} ) @@ -78,7 +110,10 @@ set(GENERATED_SOURCES_DEPENDENCIES ${TYPESYSTEM_FILE} ) -set(SHIBOKEN_PATH shiboken6) +find_program(SHIBOKEN_PATH shiboken6 HINTS "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/bin") +if (NOT SHIBOKEN_PATH) + set(SHIBOKEN_PATH shiboken6) +endif() # Add custom target to run shiboken. add_custom_command(OUTPUT ${GENERATED_SOURCES} @@ -144,8 +179,10 @@ target_link_libraries(${PROJECT_NAME} Qt6::Qml OpenSSL::SSL ZLIB::ZLIB - Shiboken6::libshiboken - PySide6::pyside6 + ZLIB::ZLIB + "${SHIBOKEN6_LIB_DIR}/${SHIBOKEN6_LIB_NAME}" + "${PYSIDE6_LIB_DIR}/${PYSIDE6_LIB_NAME}" + Python::Module ) default_options(${PROJECT_NAME}) @@ -159,8 +196,9 @@ include_directories("${PySide6_INCLUDE_DIR}/QtQuick") include_directories("${PySide6_INCLUDE_DIR}/QtQuickWidgets") include_directories("${PySide6_INCLUDE_DIR}/QtNetwork") include_directories("${PySide6_INCLUDE_DIR}/QtQml") -include_directories("${PySide6_INCLUDE_DIR}/PySide6") -include_directories(${SHIBOKEN_PYTHON_INCLUDE_DIRS}) +include_directories("${PySide6_INCLUDE_DIR}") +include_directories(${SHIBOKEN6_INCLUDE_DIRS}) +include_directories(${Python_INCLUDE_DIRS}) set(PYTHONVP "python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}") diff --git a/vcpkg.json b/vcpkg.json index dcb3e197a..77293f561 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { - "name": "xstudio", - "version": "1.0.0", + "name": "xstudio", + "version": "1.0.0", "dependencies": [ "stduuid", "reproc", @@ -8,6 +8,7 @@ "glew", "freetype", "libjpeg-turbo", + "libffi", "pybind11", "python3", "spdlog", @@ -50,12 +51,16 @@ ] } ], - "builtin-baseline": "f9b54c1c539dda8d61c3001bb30eb9f0c5032086", + "builtin-baseline": "f9b54c1c539dda8d61c3001bb30eb9f0c5032086", "overrides": [ { "name": "opencolorio", "version": "2.2.1#1" }, + { + "name": "python3", + "version": "3.11.11" + }, { "name": "caf", "version": "1.0.2#0" @@ -73,4 +78,4 @@ "version": "1.84.0#3" } ] -} +} \ No newline at end of file From 4c47d078967924ef78f4b9310ec41d4adf6032f1 Mon Sep 17 00:00:00 2001 From: "Sam.Richards@taurich.org" Date: Mon, 26 Jan 2026 15:05:52 +0000 Subject: [PATCH 2/6] Adding the build_pyside6 borrowed from openrv. Signed-off-by: Sam.Richards@taurich.org --- src/build/make_pyside6.py | 311 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100755 src/build/make_pyside6.py diff --git a/src/build/make_pyside6.py b/src/build/make_pyside6.py new file mode 100755 index 000000000..44e9b5525 --- /dev/null +++ b/src/build/make_pyside6.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import argparse +import glob +import os +import re +import pathlib +import shutil +import subprocess +import platform +import tempfile +import uuid +import sys + +# Ensure build dependencies are available +def install_dependencies(): + required_packages = ["py7zr", "requests"] + for package in required_packages: + try: + __import__(package) + except ImportError: + print(f"Installing missing build dependency: {package}") + subprocess.check_call([sys.executable, "-m", "pip", "install", package]) + +install_dependencies() + +from utils import ( + download_file, + extract_7z_archive, + verify_7z_archive, +) + +SOURCE_DIR = "" +OUTPUT_DIR = "" +TEMP_DIR = "" +VARIANT = "" + +QT_OUTPUT_DIR = "" +PYTHON_OUTPUT_DIR = "" +OPENSSL_OUTPUT_DIR = "" +PYTHON_EXECUTABLE = "" + +LIBCLANG_URL_BASE = "https://mirrors.ocf.berkeley.edu/qt/development_releases/prebuilt/libclang/libclang-release_" + +def get_python_interpreter_args() -> list[str]: + return [PYTHON_EXECUTABLE, "-s", "-E"] + +def prepare() -> None: + """ + Run the clean step of the build. Removes everything. + """ + if os.path.exists(TEMP_DIR) is False: + os.makedirs(TEMP_DIR) + + # PySide6 requires Clang 13+. + print("Setting up Clang...") + clang_filename_suffix = "" + fallback_clang_filename_suffix = None + + system = platform.system() + if system == "Darwin": + + def get_clang_version(): + version_output = os.popen("clang --version").read() + version_search = re.search(r"version (\d+)\.(\d+)\.(\d+)", version_output) + if version_search: + return version_search.groups() + print("ERROR: Could not extract clang --version") + return None + + def get_clang_filename_suffix(version): + version_str = ".".join(version) + return f"{version_str}-based-macos-universal.7z" + + def get_fallback_clang_filename_suffix(version): + major_minor_version_str = ".".join(version[:2]) + if major_minor_version_str == "14.0": + return "14.0.3-based-macos-universal.7z" + elif major_minor_version_str == "15.0": + return "15.0.0-based-macos-universal.7z" + elif major_minor_version_str == "17.0": + return "17.0.1-based-macos-universal.7z" + return None + + clang_version = get_clang_version() + if clang_version: + clang_filename_suffix = get_clang_filename_suffix(clang_version) + fallback_clang_filename_suffix = get_fallback_clang_filename_suffix(clang_version) + + elif system == "Linux": + clang_filename_suffix = "19.1.0-based-linux-Rhel8.8-gcc10.3-x86_64.7z" + elif system == "Windows": + clang_filename_suffix = "19.1.0-based-windows-vs2019_64.7z" + + download_url = LIBCLANG_URL_BASE + clang_filename_suffix + libclang_zip = os.path.join(TEMP_DIR, "libclang.7z") + + # if we have a failed download, clean it up and redownload. + # checking for False since it can return None when archive doesn't have a CRC + if os.path.exists(libclang_zip) and verify_7z_archive(libclang_zip) is False: + os.remove(libclang_zip) + + # download it if necessary + if os.path.exists(libclang_zip) is False: + download_ok = download_file(download_url, libclang_zip) + if not download_ok and fallback_clang_filename_suffix: + fallback_download_url = LIBCLANG_URL_BASE + fallback_clang_filename_suffix + print(f"WARNING: Could not download or version does not exist: {download_url}") + print(f"WARNING: Attempting to fallback on known version: {fallback_download_url}...") + download_ok = download_file(fallback_download_url, libclang_zip) + if not download_ok: + print(f"ERROR: Could not download or version does not exist: {download_url}") + + # clean up previous failed extraction + libclang_tmp = os.path.join(TEMP_DIR, "libclang-tmp") + if os.path.exists(libclang_tmp) is True: + shutil.rmtree(libclang_tmp) + + # extract to a temp location and only move if successful + libclang_extracted = os.path.join(TEMP_DIR, "libclang") + if os.path.exists(libclang_extracted) is False: + extract_7z_archive(libclang_zip, libclang_tmp) + shutil.move(libclang_tmp, libclang_extracted) + + libclang_install_dir = os.path.join(libclang_extracted, "libclang") + if not os.path.isdir(libclang_install_dir): + libclang_install_dir = libclang_extracted + + if OPENSSL_OUTPUT_DIR: + os.environ["PATH"] = os.path.pathsep.join( + [ + os.path.join(OPENSSL_OUTPUT_DIR, "bin"), + os.environ.get("PATH", ""), + ] + ) + + print(f"PATH={os.environ['PATH']}") + + os.environ["LLVM_INSTALL_DIR"] = libclang_install_dir + os.environ["CLANG_INSTALL_DIR"] = libclang_install_dir + + # Ensure CMake finds Clang + current_prefix_path = os.environ.get("CMAKE_PREFIX_PATH", "") + os.environ["CMAKE_PREFIX_PATH"] = os.path.pathsep.join([libclang_install_dir, current_prefix_path]) + + # PySide6 build requires numpy 1.26.3 (or compatible) + numpy_version = os.environ.get("RV_DEPS_NUMPY_VERSION", "1.26.4") + + # Check if numpy is already installed + check_numpy = subprocess.run([PYTHON_EXECUTABLE, "-c", "import numpy"], capture_output=True) + if check_numpy.returncode != 0: + print(f"Numpy not found, installing numpy=={numpy_version}...") + install_numpy_args = get_python_interpreter_args() + [ + "-m", + "pip", + "install", + f"numpy=={numpy_version}", + ] + # If we are using a prefix, we might need to tell pip to install there too? + # But for now assuming we can install to the python env or it's a venv. + # If we want to fully isolate, we should likely create a venv. + # But let's try installing to the env for dependencies. + print(f"Installing numpy with {install_numpy_args}") + subprocess.run(install_numpy_args).check_returncode() + else: + print("Numpy already installed, skipping installation.") + + + cmakelist_path = os.path.join(SOURCE_DIR, "sources", "shiboken6", "ApiExtractor", "CMakeLists.txt") + old_cmakelist_path = os.path.join(SOURCE_DIR, "sources", "shiboken6", "ApiExtractor", "CMakeLists.txt.old") + if os.path.exists(old_cmakelist_path): + os.remove(old_cmakelist_path) + + if os.path.exists(cmakelist_path): + os.rename(cmakelist_path, old_cmakelist_path) + with open(old_cmakelist_path) as old_cmakelist: + with open(cmakelist_path, "w") as cmakelist: + for line in old_cmakelist: + new_line = line.replace( + " set(HAS_LIBXSLT 1)", + " #set(HAS_LIBXSLT 1)", + ) + + cmakelist.write(new_line) + + +def remove_broken_shortcuts(python_home: str) -> None: + # Simplified version, might not be needed for xstudio but keeping safe parts + pass + + +def build() -> None: + """ + Run the build step of the build. It compile every target of the project. + """ + python_interpreter_args = get_python_interpreter_args() + + # Ensure we use the right QTSP for macos + qtpaths_bin = os.path.join(QT_OUTPUT_DIR, 'bin', 'qtpaths' + ('.exe' if platform.system() == 'Windows' else '')) + + # Make sure we install to the output dir + pyside_build_args = python_interpreter_args + [ + os.path.join(SOURCE_DIR, "setup.py"), + "install", + f"--prefix={OUTPUT_DIR}", + f"--qtpaths={qtpaths_bin}", + "--ignore-git", + "--standalone", + "--verbose", + "--verbose-build", + "--log-level=verbose", + f"--parallel={os.cpu_count() or 1}", + ] + + # Ensure PYTHONPATH includes the new install location so build steps can find shiboken if needed for pyside6? + # Actually setup.py handles the build order (shiboken then pyside). + # But we might need to set PYTHONPATH for the subprocess if it invokes python. + + env = os.environ.copy() + # Construct site-packages path in OUTPUT_DIR + # This varies by platform/python version. + # E.g. lib/python3.12/site-packages + # For now, let's assume standard layout. + + if OPENSSL_OUTPUT_DIR: + pyside_build_args.append(f"--openssl={os.path.join(OPENSSL_OUTPUT_DIR, 'bin')}") + + if platform.system() == "Windows": + if VARIANT == "Debug": + pyside_build_args.append("--debug") + + print(f"Executing {pyside_build_args}") + subprocess.run(pyside_build_args, check=True, env=env) + + # Cleanup generator + generator_cleanup_args = python_interpreter_args + [ + "-m", + "pip", + "uninstall", + "-y", + "shiboken6_generator", + ] + + print(f"Executing {generator_cleanup_args}") + subprocess.run(generator_cleanup_args, check=False) # Don't fail if not present + + # Even if we remove shiboken6_generator from pip, the files stays... for some reasons + generator_cleanup_args = python_interpreter_args + [ + "-c", + "\n".join( + [ + "import os, shutil", + "try:", + " import shiboken6_generator", + "except:", + " exit(0)", + "shutil.rmtree(os.path.dirname(shiboken6_generator.__file__))", + ] + ), + ] + + print(f"Executing {generator_cleanup_args}") + subprocess.run(generator_cleanup_args, check=False) + + if OPENSSL_OUTPUT_DIR and platform.system() == "Windows": + # ... (OpenSSL copy logic, probably skipped for now) + pass + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + parser.add_argument("--prepare", dest="prepare", action="store_true") + parser.add_argument("--build", dest="build", action="store_true") + + parser.add_argument("--source-dir", dest="source", type=pathlib.Path, required=True) + parser.add_argument("--python-dir", dest="python", type=pathlib.Path, required=False, default=pathlib.Path(".")) # Kept for compat but maybe unused + parser.add_argument("--qt-dir", dest="qt", type=pathlib.Path, required=True) + parser.add_argument("--openssl-dir", dest="openssl", type=pathlib.Path, required=False) + parser.add_argument("--temp-dir", dest="temp", type=pathlib.Path, required=True) + parser.add_argument("--output-dir", dest="output", type=pathlib.Path, required=True) + + parser.add_argument("--variant", dest="variant", type=str, required=True) + + parser.add_argument("--python-executable", dest="python_executable", type=str, required=True) + + # Major and minor version with dots. + parser.add_argument("--python-version", dest="python_version", type=str, required=False, default="") + + parser.set_defaults(prepare=False, build=False) + + args = parser.parse_args() + + SOURCE_DIR = args.source + OUTPUT_DIR = args.output + TEMP_DIR = args.temp + OPENSSL_OUTPUT_DIR = args.openssl + PYTHON_OUTPUT_DIR = args.python + QT_OUTPUT_DIR = args.qt + VARIANT = args.variant + PYTHON_EXECUTABLE = args.python_executable + + print(args) + + if args.prepare: + prepare() + + if args.build: + build() + From e16521364c08c72566d219b45d2fdfab4a88dd3c Mon Sep 17 00:00:00 2001 From: "Sam.Richards@taurich.org" Date: Mon, 26 Jan 2026 17:37:54 +0000 Subject: [PATCH 3/6] More fixes for pyside6 on osx. Signed-off-by: Sam.Richards@taurich.org --- src/build/make_pyside6.py | 51 ++++++++++++++------------- src/pyside6_module/src/CMakeLists.txt | 12 ++++--- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/build/make_pyside6.py b/src/build/make_pyside6.py index 44e9b5525..559254521 100755 --- a/src/build/make_pyside6.py +++ b/src/build/make_pyside6.py @@ -234,34 +234,35 @@ def build() -> None: subprocess.run(pyside_build_args, check=True, env=env) # Cleanup generator - generator_cleanup_args = python_interpreter_args + [ - "-m", - "pip", - "uninstall", - "-y", - "shiboken6_generator", - ] + # Cleanup generator + # generator_cleanup_args = python_interpreter_args + [ + # "-m", + # "pip", + # "uninstall", + # "-y", + # "shiboken6_generator", + # ] - print(f"Executing {generator_cleanup_args}") - subprocess.run(generator_cleanup_args, check=False) # Don't fail if not present + # print(f"Executing {generator_cleanup_args}") + # subprocess.run(generator_cleanup_args, check=False) # Don't fail if not present # Even if we remove shiboken6_generator from pip, the files stays... for some reasons - generator_cleanup_args = python_interpreter_args + [ - "-c", - "\n".join( - [ - "import os, shutil", - "try:", - " import shiboken6_generator", - "except:", - " exit(0)", - "shutil.rmtree(os.path.dirname(shiboken6_generator.__file__))", - ] - ), - ] - - print(f"Executing {generator_cleanup_args}") - subprocess.run(generator_cleanup_args, check=False) + # generator_cleanup_args = python_interpreter_args + [ + # "-c", + # "\n".join( + # [ + # "import os, shutil", + # "try:", + # " import shiboken6_generator", + # "except:", + # " exit(0)", + # "shutil.rmtree(os.path.dirname(shiboken6_generator.__file__))", + # ] + # ), + # ] + + # print(f"Executing {generator_cleanup_args}") + # subprocess.run(generator_cleanup_args, check=False) if OPENSSL_OUTPUT_DIR and platform.system() == "Windows": # ... (OpenSSL copy logic, probably skipped for now) diff --git a/src/pyside6_module/src/CMakeLists.txt b/src/pyside6_module/src/CMakeLists.txt index c94ab1e73..64a076ca0 100644 --- a/src/pyside6_module/src/CMakeLists.txt +++ b/src/pyside6_module/src/CMakeLists.txt @@ -110,16 +110,20 @@ set(GENERATED_SOURCES_DEPENDENCIES ${TYPESYSTEM_FILE} ) -find_program(SHIBOKEN_PATH shiboken6 HINTS "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/bin") +find_program(SHIBOKEN_PATH shiboken6 HINTS "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/bin" "${CMAKE_BINARY_DIR}/python/bin") if (NOT SHIBOKEN_PATH) - set(SHIBOKEN_PATH shiboken6) + # If not found during configure, assume it will be built at this location + set(SHIBOKEN_PATH "${CMAKE_BINARY_DIR}/python/bin/shiboken6") endif() + +set(PYTHONVP "python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}") + # Add custom target to run shiboken. add_custom_command(OUTPUT ${GENERATED_SOURCES} - COMMAND ${SHIBOKEN_PATH} + COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_BINARY_DIR}/python/lib/${PYTHONVP}/site-packages ${SHIBOKEN_PATH} ${SHIBOKEN_OPTIONS} ${WRAPPED_HEADER} ${TYPESYSTEM_FILE} - DEPENDS ${GENERATED_SOURCES_DEPENDENCIES} + DEPENDS ${GENERATED_SOURCES_DEPENDENCIES} pyside6_local_build WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Running generator for ${TYPESYSTEM_FILE}.") From d6d9d449ef2f0253db8e791a467ed43fc87b8ba4 Mon Sep 17 00:00:00 2001 From: "Sam.Richards@taurich.org" Date: Mon, 26 Jan 2026 17:38:25 +0000 Subject: [PATCH 4/6] ffmpeg attribute rename. Signed-off-by: Sam.Richards@taurich.org --- src/plugin/media_metadata/ffprobe/src/ffprobe_lib.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/media_metadata/ffprobe/src/ffprobe_lib.cpp b/src/plugin/media_metadata/ffprobe/src/ffprobe_lib.cpp index 7c91200e7..f3d66df28 100644 --- a/src/plugin/media_metadata/ffprobe/src/ffprobe_lib.cpp +++ b/src/plugin/media_metadata/ffprobe/src/ffprobe_lib.cpp @@ -276,7 +276,7 @@ nlohmann::json populate_stream(AVFormatContext *avfc, int index, MediaStream *is result["profile"] = nullptr; if (profile = avcodec_profile_name(par->codec_id, par->profile)) result["profile"] = profile; - else if (par->profile != FF_PROFILE_UNKNOWN) { + else if (par->profile != AV_PROFILE_UNKNOWN) { result["profile"] = std::to_string(par->profile); } From 69500d97658e5936bfc7b277281486015ade928f Mon Sep 17 00:00:00 2001 From: "Sam.Richards@taurich.org" Date: Mon, 26 Jan 2026 17:40:32 +0000 Subject: [PATCH 5/6] Missing requirement for PySide6 build. Signed-off-by: Sam.Richards@taurich.org --- cmake/BuildPySide6.cmake | 142 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 cmake/BuildPySide6.cmake diff --git a/cmake/BuildPySide6.cmake b/cmake/BuildPySide6.cmake new file mode 100644 index 000000000..8a412b7af --- /dev/null +++ b/cmake/BuildPySide6.cmake @@ -0,0 +1,142 @@ +include(FetchContent) + +# URL and Hash for PySide6 6.5.3 matching OpenRV +FetchContent_Declare( + pyside6_source + URL https://mirrors.ocf.berkeley.edu/qt/official_releases/QtForPython/pyside6/PySide6-6.5.3-src/pyside-setup-everywhere-src-6.5.3.zip + URL_HASH MD5=515d3249c6e743219ff0d7dd25b8c8d8 + PATCH_COMMAND sed -i.bak "s/check_allowed_python_version()/# check_allowed_python_version()/" setup.py +) + +# FetchContent_MakeAvailable(pyside6_source) # This adds it as subproject which we don't want + +FetchContent_GetProperties(pyside6_source) +if(NOT pyside6_source_POPULATED) + FetchContent_Populate(pyside6_source) +endif() + +# We install PySide6 into a local directory in the build tree +set(PYSIDE6_INSTALL_DIR ${CMAKE_BINARY_DIR}/python) +# Ensure this directory exists +file(MAKE_DIRECTORY ${PYSIDE6_INSTALL_DIR}) + +# We need to find the root of Qt installation to pass to the script +# Qt6_DIR points to lib/cmake/Qt6. Use cmake path command to traverse up. +# or simply assume standard layout if we can't deduce it. +# Actually make_pyside6.py expects --qt-dir to contain bin/qtpaths. +# If Qt6_DIR is .../lib/cmake/Qt6, then root is .../../../.. +get_filename_component(_qt6_lib_cmake_dir ${Qt6_DIR} DIRECTORY) # .../lib/cmake +get_filename_component(_qt6_lib_dir ${_qt6_lib_cmake_dir} DIRECTORY) # .../lib +get_filename_component(_qt6_root_dir ${_qt6_lib_dir} DIRECTORY) # .../ (root) + +# Check if qtpaths exists there to be sure +if(EXISTS "${_qt6_root_dir}/bin/qtpaths" OR EXISTS "${_qt6_root_dir}/bin/qtpaths.exe") + set(QT_ROOT_DIR ${_qt6_root_dir}) +else() + # It might be in a different layout (e.g. system install vs local install) + # Qt6_DIR usually works. + message(STATUS "Could not deduce Qt root from Qt6_DIR=${Qt6_DIR}. Probing...") + # Try finding qtpaths directly + find_program(QTPATHS_EXECUTABLE NAMES qtpaths qtpaths6 HINTS ${_qt6_root_dir}/bin) + if (QTPATHS_EXECUTABLE) + get_filename_component(_qtpaths_dir ${QTPATHS_EXECUTABLE} DIRECTORY) + get_filename_component(QT_ROOT_DIR ${_qtpaths_dir} DIRECTORY) + else() + message(FATAL_ERROR "Could not find qtpaths executable. PySide6 build requires it.") + endif() +endif() + +message(STATUS "Building PySide6 6.5.3 using Qt from ${QT_ROOT_DIR}") + +# We predict the location of the libraries based on the install structure. +# setup.py install usually goes to lib/pythonX.Y/site-packages +set(PYTHON_SITE_PACKAGES "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages") +# Use Python3_VERSION if Python_VERSION is not set (sometimes FindPython3 is used) +if (NOT Python_VERSION_MAJOR AND Python3_VERSION_MAJOR) + set(PYTHON_SITE_PACKAGES "lib/python${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}/site-packages") +endif() + +set(PYSIDE6_LIB_DIR "${PYSIDE6_INSTALL_DIR}/${PYTHON_SITE_PACKAGES}/PySide6") +set(SHIBOKEN6_LIB_DIR "${PYSIDE6_INSTALL_DIR}/${PYTHON_SITE_PACKAGES}/shiboken6") + +message(STATUS "BuildPySide6: Expecting output at ${PYSIDE6_LIB_DIR}") + + +# Library extension +if(APPLE) + set(LIB_EXT ".dylib") + set(SHARED_LIB_EXT ".so") # Python modules are .so + set(PYSIDE6_LIB_NAME "libpyside6.abi3.6.5${LIB_EXT}") + set(SHIBOKEN6_LIB_NAME "libshiboken6.abi3.6.5${LIB_EXT}") +elseif(UNIX) + set(LIB_EXT ".so") + set(PYSIDE6_LIB_NAME "libpyside6.abi3${LIB_EXT}") + set(SHIBOKEN6_LIB_NAME "libshiboken6.abi3${LIB_EXT}") +elseif(WIN32) + set(LIB_EXT ".lib") + set(PYSIDE6_LIB_NAME "pyside6.lib") # verify windows names + set(SHIBOKEN6_LIB_NAME "shiboken6.lib") +endif() + +# Define the custom command to build PySide6 +add_custom_command( + OUTPUT ${PYSIDE6_LIB_DIR}/__init__.py + ${PYSIDE6_LIB_DIR}/${PYSIDE6_LIB_NAME} + ${SHIBOKEN6_LIB_DIR}/${SHIBOKEN6_LIB_NAME} + COMMAND ${Python_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/build/make_pyside6.py + --prepare + --build + --source-dir ${pyside6_source_SOURCE_DIR} + --qt-dir ${QT_ROOT_DIR} + --python-executable ${Python_EXECUTABLE} + --temp-dir ${CMAKE_BINARY_DIR}/pyside6_build_temp + --variant ${CMAKE_BUILD_TYPE} + --output-dir ${PYSIDE6_INSTALL_DIR} + COMMAND ${CMAKE_COMMAND} -E create_symlink ../shiboken6/${SHIBOKEN6_LIB_NAME} ${PYSIDE6_LIB_DIR}/${SHIBOKEN6_LIB_NAME} + COMMENT "Building PySide6 6.5.3 to ${PYSIDE6_LIB_DIR}..." + VERBATIM +) + +# Use checking __init__.py as marker for existence +add_custom_target(pyside6_local_build ALL DEPENDS ${PYSIDE6_LIB_DIR}/__init__.py) + +# PySide6 Target + +add_library(PySide6::pyside6 SHARED IMPORTED GLOBAL) +set_target_properties(PySide6::pyside6 PROPERTIES + IMPORTED_LOCATION "${PYSIDE6_LIB_DIR}/${PYSIDE6_LIB_NAME}" + INTERFACE_INCLUDE_DIRECTORIES "${PYSIDE6_LIB_DIR}/include" +) +add_dependencies(PySide6::pyside6 pyside6_local_build) + +set(SHIBOKEN6_GENERATOR_INCLUDE_DIR "${PYSIDE6_INSTALL_DIR}/${PYTHON_SITE_PACKAGES}/shiboken6_generator/include") + +# Shiboken6 Target +add_library(Shiboken6::libshiboken SHARED IMPORTED GLOBAL) +set_target_properties(Shiboken6::libshiboken PROPERTIES + IMPORTED_LOCATION "${SHIBOKEN6_LIB_DIR}/${SHIBOKEN6_LIB_NAME}" + INTERFACE_INCLUDE_DIRECTORIES "${SHIBOKEN6_LIB_DIR}/include;${SHIBOKEN6_GENERATOR_INCLUDE_DIR}" +) +add_dependencies(Shiboken6::libshiboken pyside6_local_build) + +# Set variables used by downstream (legacy support if targets aren't enough) +set(PySide6_INCLUDE_DIR "${PYSIDE6_LIB_DIR}/include") +set(SHIBOKEN6_INCLUDE_DIRS "${SHIBOKEN6_LIB_DIR}/include") +set(PySide6_LIBRARIES PySide6::pyside6) +set(Shiboken6_LIBRARIES Shiboken6::libshiboken) + +# Ensure these directories exist so CMake doesn't complain about non-existent include paths +# They will be populated during the build step. +file(MAKE_DIRECTORY "${PySide6_INCLUDE_DIR}") +file(MAKE_DIRECTORY "${SHIBOKEN6_INCLUDE_DIRS}") +file(MAKE_DIRECTORY "${SHIBOKEN6_GENERATOR_INCLUDE_DIR}") + +# Install PySide6 and Shiboken6 into the app bundle Resources/python/lib +if(APPLE) + install(DIRECTORY "${PYSIDE6_INSTALL_DIR}/${PYTHON_SITE_PACKAGES}/PySide6" + DESTINATION Resources/python/lib) + install(DIRECTORY "${PYSIDE6_INSTALL_DIR}/${PYTHON_SITE_PACKAGES}/shiboken6" + DESTINATION Resources/python/lib) + # Explicitly install __init__.py to ensure it is present + install(FILES "${PYSIDE6_LIB_DIR}/__init__.py" DESTINATION Resources/python/lib/PySide6) +endif() From 47b9eb917b4becec95bf4bac04f19d800d6fcda7 Mon Sep 17 00:00:00 2001 From: "Sam.Richards@taurich.org" Date: Mon, 26 Jan 2026 17:44:14 +0000 Subject: [PATCH 6/6] Used by the make_pyside6.py code. Signed-off-by: Sam.Richards@taurich.org --- src/build/utils.py | 424 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 src/build/utils.py diff --git a/src/build/utils.py b/src/build/utils.py new file mode 100644 index 000000000..521e4d255 --- /dev/null +++ b/src/build/utils.py @@ -0,0 +1,424 @@ +# -*- coding: utf-8 -*- + +# ***************************************************************************** +# Copyright 2020 Autodesk, Inc. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# ***************************************************************************** + +import itertools +import os +import platform +import requests +import shutil +import subprocess +import sys + +from typing import List +from zipfile import ZipFile + + +def _windows_zip_impl(input_folder: str, output_zip: str) -> None: + """ + Windows implementation of the zip function. + + This implementation relies on shutil.make_archive because nothing special have to be done on Windows. + + :param str input_folder: Folder to archive + :param str output_zip: Archive file name + """ + result = shutil.make_archive(output_zip, "zip", input_folder) + os.rename(result, output_zip) # make_archive append .zip and this is not desirable. + + +def _windows_unzip_impl(input_zip: str, output_folder: str) -> None: + """ + Windows implementation of the zip function. + + This implementation relies on ZipFile because nothing special have to be done on Windows. + + :param input_zip: Path to the zip file to uncompress + :param output_folder: Location to wich the zip have to be uncompressed + """ + with ZipFile(input_zip) as zip_file: + zip_file.extractall(output_folder) + + +def _darwin_zip_impl(input_folder: str, output_zip: str) -> None: + """ + macOS implementation of the zip function. + + This implementation relies on ditto so all the MacOS special sauce can be captured inside the archive. + + :param str input_folder: Folder to archive + :param str output_zip: Archive file name + """ + zip_args = ["/usr/bin/ditto", "-c", "-k", input_folder, output_zip] + + print(f"Executing {zip_args}") + subprocess.run(zip_args).check_returncode() + + +def _darwin_unzip_impl(input_zip: str, output_folder: str) -> None: + """ + macOS implementation of the zip function. + + This implementation relies on ditto so all the MacOS special sauce can be extracted from the archive. + + :param input_zip: Path to the zip file to uncompress + :param output_folder: Location to which the zip have to be uncompressed + """ + unzip_args = ["/usr/bin/ditto", "-x", "-k", input_zip, output_folder] + + print(f"Executing {unzip_args}") + subprocess.run(unzip_args).check_returncode() + + +def _linux_zip_impl(input_folder: str, output_zip: str) -> None: + """ + Linux implementation of the zip function. + + This implementation relies on the zip command line tool. This is prefered because that way, we can archive keeping + the symlinks as-is. + + :param str input_folder: Folder to archive + :param str output_zip: Archive file name + """ + zip_args = ["zip", "--symlinks", "-r", output_zip, "."] + + print(f"Executing {zip_args}") + subprocess.run(zip_args, cwd=input_folder).check_returncode() + + +def _linux_unzip_impl(input_zip: str, output_folder: str) -> None: + """ + Linux implementation of the zip function. + + This implementation relies on the unzip command line tool. This is prefered because ZipFile doesn't preserve + symlinks. + + :param input_zip: Path to the zip file to uncompress + :param output_folder: Location to which the zip have to be uncompressed + """ + unzip_args = ["unzip", "-q", input_zip, "-d", output_folder] + + print(f"Executing {unzip_args}") + subprocess.run(unzip_args).check_returncode() + + +def create_zip_archive(input_folder: str, output_zip: str) -> None: + """ + Implementation of the zip function. + + :param str input_folder: Folder to archive + :param str output_zip: Archive file name + """ + print(f"Archiving {input_folder} into {output_zip}") + + if platform.system() == "Linux": + _linux_zip_impl(input_folder, output_zip) + elif platform.system() == "Darwin": + _darwin_zip_impl(input_folder, output_zip) + else: + _windows_zip_impl(input_folder, output_zip) + + +def extract_zip_archive(input_zip: str, output_folder: str) -> None: + """ + Implementation of the unzip function. + + :param input_zip: Path to the zip file to uncompress + :param output_folder: Location to which the zip have to be uncompressed + """ + print(f"Extracting {input_zip} into {output_folder}") + + if platform.system() == "Linux": + _linux_unzip_impl(input_zip, output_folder) + elif platform.system() == "Darwin": + _darwin_unzip_impl(input_zip, output_folder) + else: + _windows_unzip_impl(input_zip, output_folder) + + +def extract_7z_archive(input_7z: str, output_folder: str) -> None: + """ + Implementation of the uncompress function for 7z files using cmake. + + :param input_zip: Path to the 7z file to uncompress + :param output_folder: Location to which the 7z have to be uncompressed + """ + print(f"Extracting {input_7z} into {output_folder}") + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + # Using cmake -E tar xf which supports 7z format + run_args = ["cmake", "-E", "tar", "xf", input_7z] + print(f"Executing {run_args}") + # Extract to the directory + subprocess.run(run_args, cwd=output_folder, check=True) + + +def verify_7z_archive(input_7z: str) -> bool: + """ + Simple verification for 7z archive (existence and non-empty). + + :param input_zip: Path to the 7z file to uncompress + """ + print(f"Verifying {input_7z}") + return os.path.exists(input_7z) and os.path.getsize(input_7z) > 0 + + +def _darwin_copy_recursively(src_folder: str, destination_folder) -> None: + """ + Implementation of the copy function. + + This implementation uses ditto as it's the preferred copy command on MacOS X + + :param src_folder: Path of source of the copy + :param destination_folder: Path to the destination the copy + """ + copy_args = ["ditto", src_folder, destination_folder] + + print(f"Executing {copy_args}") + subprocess.run(copy_args).check_returncode() + + +def _linux_copy_recursively(src_folder: str, destination_folder) -> None: + """ + Implementation of the copy function. + + This implementation uses cp as it's the preferred copy command on Linux. + + :param src_folder: Path of source of the copy + :param destination_folder: Path to the destination the copy + """ + copy_args = ["cp", "-r", src_folder, destination_folder] + + print(f"Executing {copy_args}") + subprocess.run(copy_args).check_returncode() + + +def _windows_copy_recursively(src_folder: str, destination_folder) -> None: + """ + Implementation of the copy function. + + :param src_folder: Path of source of the copy + :param destination_folder: Path to the destination the copy + """ + raise NotImplementedError() + + +def copy_recursively(src_folder: str, destination_folder: str) -> None: + """ + Implementation of the copy function. + + :param src_folder: Path of source of the copy + :param destination_folder: Path to the destination the copy + """ + + print(f"Copying {src_folder} into {destination_folder}") + + if platform.system() == "Linux": + _linux_copy_recursively(src_folder, destination_folder) + elif platform.system() == "Darwin": + _darwin_copy_recursively(src_folder, destination_folder) + else: + _windows_copy_recursively(src_folder, destination_folder) + + +def download_file(url, file_path): + """ + Download a file with HTTP or HTTPS. + + Returns True if the HTTP request succeeded, False otherwise + + param: url: url to download + param file_path: destination file path + """ + + print(f"Downloading {url} into {file_path}") + req = requests.get(url, stream=True) + + with open(file_path, "wb") as file: + total = req.headers.get("content-length") + if total is None: + file.write(req.content) + else: + total = int(total) + current = 0 + + for chunk in req.iter_content(round(total / 100)): + current += len(chunk) + file.write(chunk) + print(f"{current} / {total} ({round((current / total) * 100)}%)") + + return req.ok + + +def get_cygpath_windows(cygpath: str) -> str: + """ + Returns the windows path corresponding to the cygpath passed as parameter. + :param string cygpath: cygpath to be translated. Example: C:/git/OpenRV/_build + """ + return subprocess.check_output(["cygpath", "--windows", "--absolute", f"{cygpath}"]).rstrip().decode("utf-8") + + +def update_env_path(newpaths: str) -> None: + """ + Augment the PATH environment variable with newpaths + """ + paths = os.environ["PATH"].lower().split(os.pathsep) + for path in newpaths: + if path.lower() not in paths: + paths.insert(0, path) + os.environ["PATH"] = "{}{}{}".format(path, os.pathsep, os.environ["PATH"]) + + +def source_widows_msvc_env(msvc_year: str) -> None: + """ + Source the Microsoft Windows Visual Studio x environment so that a subprocess build + inherits the proper msvc environement to build. + Note: Equivalent to call vcvars64.bat from a command prompt. + :param string msvc_year: Visual Studio x environment to be sourced. Example: 2019 + """ + + def get_environment_from_batch_command(env_cmd, initial=None): + """ + Take a command (either a single command or list of arguments) + and return the environment created after running that command. + Note that the command must be a batch file or .cmd file, or the + changes to the environment will not be captured. + + If initial is supplied, it is used as the initial environment passed + to the child process. + """ + + def validate_pair(ob): + try: + if not (len(ob) == 2): + print("Unexpected result: {}".format(ob)) + raise ValueError + except Exception: + return False + return True + + def consume(iter): + try: + while True: + next(iter) + except StopIteration: + pass + + if not isinstance(env_cmd, (list, tuple)): + env_cmd = [env_cmd] + # construct the command that will alter the environment + env_cmd = subprocess.list2cmdline(env_cmd) + # create a tag so we can tell in the output when the proc is done + tag = "Done running command" + # construct a cmd.exe command to do accomplish this + cmd = 'cmd.exe /E:ON /V:ON /s /c "{} && echo "{}" && set"'.format(env_cmd, tag) + # launch the process + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=initial) + # parse the output sent to stdout + lines = proc.stdout + if sys.version_info[0] > 2: + # make sure the lines are strings + lines = map(lambda s: s.decode(), lines) + # consume whatever output occurs until the tag is reached + consume(itertools.takewhile(lambda line: tag not in line, lines)) + # define a way to handle each KEY=VALUE line + # parse key/values into pairs + pairs = map(lambda line: line.rstrip().split("=", 1), lines) + # make sure the pairs are valid + valid_pairs = filter(validate_pair, pairs) + # construct a dictionary of the pairs + result = dict(valid_pairs) + # let the process finish + proc.communicate() + return result + + mvs_base_path = os.path.join( + os.path.expandvars("%SystemDrive%") + os.sep, + "Program Files", + "Microsoft Visual Studio", + ) + vcvars_path = os.path.join( + mvs_base_path, + msvc_year, + "Professional", + "VC", + "Auxiliary", + "Build", + "vcvars64.bat", + ) + if not os.path.exists(vcvars_path): + vcvars_path = os.path.join( + mvs_base_path, + msvc_year, + "Entreprise", + "VC", + "Auxiliary", + "Build", + "vcvars64.bat", + ) + if not os.path.exists(vcvars_path): + vcvars_path = os.path.join( + mvs_base_path, + msvc_year, + "Community", + "VC", + "Auxiliary", + "Build", + "vcvars64.bat", + ) + + if not os.path.exists(vcvars_path): + print("ERROR: Failed to find the MSVC compiler environment init script (vcvars64.bat) on your system.") + return + else: + print(f"Found MSVC compiler environment init script (vcvars64.bat):\n{vcvars_path}") + + # Get MSVC env + vcvars_cmd = [vcvars_path] + msvc_env = get_environment_from_batch_command(vcvars_cmd) + msvc_env_paths = os.pathsep.join([msvc_env[k] for k in msvc_env if k.upper() == "PATH"]).split(os.pathsep) + msvc_env_without_paths = dict([(k, msvc_env[k]) for k in msvc_env if k.upper() != "PATH"]) + + # Extend os.environ with MSVC env + update_env_path(msvc_env_paths) + for k in sorted(msvc_env_without_paths): + v = msvc_env_without_paths[k] + os.environ[k] = v + + +def get_mingw64_path_on_windows(winpath: str) -> str: + """ + On Windows: returns the mingw64 path corresponding to the windows passed as parameter. + On other platforms: simply returns the path passed as parameter + :param string winpath: winpath to be translated. Example: C:\git\OpenRV\_build + """ + if platform.system() != "Windows": + return winpath + + return ( + subprocess.check_output(["c:\\msys64\\usr\\bin\\cygpath.exe", "--mixed", "--absolute", f"{winpath}"]) + .rstrip() + .decode("utf-8") + ) + + +def get_mingw64_args(cmd_args: List[str]) -> List[str]: + updated_cmd_args = cmd_args + updated_cmd_args.append(";sleep 10") + + mingw64_cmd_args = [ + "c:\\msys64\\msys2_shell.cmd", + "-defterm", + "-mingw64", + "-here", + "-c", + " ".join(updated_cmd_args), + ] + + return mingw64_cmd_args