From 4805a7c909b32821b22d8f02ce45c579822688f2 Mon Sep 17 00:00:00 2001 From: Sean Donnelly <23455376+seando-adsk@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:43:58 -0400 Subject: [PATCH 1/4] EMSUSD-282 - MayaUsd: build with Qt directly from Maya devkit/runtime * New logic in build.py (during configure stage) to "setup Maya Qt" by looking for Qt headers in Maya devkit. If not found, extract either just the header from "qt*-include.zip" for Qt5 or the entire zipfile for Qt6. * Deprecated the "--qt-location" flag from build.py. * New cmake module "FindMaya_Qt" used for Qt5. * Use versionless Qt targets. --- CMakeLists.txt | 38 ++++- build.py | 116 ++++++++++++- cmake/modules/FindMaya_Qt.cmake | 190 +++++++++++++++++++++ lib/mayaUsd/CMakeLists.txt | 2 +- lib/usd/CMakeLists.txt | 2 +- lib/usd/ui/CMakeLists.txt | 12 +- lib/usd/ui/importDialogDemo/CMakeLists.txt | 6 +- plugin/adsk/plugin/CMakeLists.txt | 4 +- 8 files changed, 338 insertions(+), 32 deletions(-) create mode 100644 cmake/modules/FindMaya_Qt.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index e8c4833953..aecaf3e913 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,7 +156,7 @@ endif() if(DEFINED QT_LOCATION) if(NOT DEFINED QT_VERSION) - if(BUILD_WITH_QT_6) + if(BUILD_WITH_QT_6) set(QT_VERSION "6.5") else() # Maya 2022, 2023, 2024 set(QT_VERSION "5.15") @@ -164,19 +164,41 @@ if(DEFINED QT_LOCATION) message(STATUS "Setting Qt version to Qt ${QT_VERSION}") endif() set(CMAKE_PREFIX_PATH "${QT_LOCATION}") + if(BUILD_WITH_QT_6) find_package(Qt6 ${QT_VERSION} COMPONENTS Core Gui Widgets REQUIRED) - if(Qt6_FOUND) - message(STATUS "Building with Qt ${QT_VERSION} features enabled.") - endif() else() find_package(Qt5 ${QT_VERSION} COMPONENTS Core Gui Widgets REQUIRED) - if(Qt5_FOUND) - message(STATUS "Building with Qt ${QT_VERSION} features enabled.") - endif() endif() + if(Qt5_FOUND OR Qt6_FOUND) + message(STATUS "Found Qt in custom location, building with Qt ${QT_VERSION} features enabled.") + set(Qt_FOUND TRUE) + else() + message(WARNING "Could not find Qt in custom location: ${QT_LOCATION}. Building with Qt features will be disabled.") + endif() else() - message(STATUS "QT_LOCATION not set. Building Qt features will be disabled.") + # First look for Qt6 in the Maya devkit. + # The Qt6 archive in the Maya devkit contains everything needed for the normal cmake find_package. + set(CMAKE_PREFIX_PATH "${MAYA_DEVKIT_LOCATION}/Qt") + find_package(Qt6 6.5 COMPONENTS Core Gui Widgets QUIET) + if (Qt6_FOUND) + message(STATUS "Found Qt6 in Maya devkit, building with Qt ${Qt6_VERSION} features enabled.") + set(BUILD_WITH_QT_6 ON) + set(Qt_FOUND TRUE) + else() + # If we didn't find Qt6 in Maya devkit, search again, but for Qt5 this time. + # Minimum version required is 5.15 (Maya 2022/2023/2024). + # This will find Qt in the Maya devkit using a custom find package. + # So the version will match Maya we are building for. + find_package(Maya_Qt 5.15 COMPONENTS Core Gui Widgets QUIET) + if (Maya_Qt_FOUND) + message(STATUS "Found Qt5 in Maya devkit, building with Qt ${MAYA_QT_VERSION} features enabled.") + set(BUILD_WITH_QT_6 OFF) + set(Qt_FOUND TRUE) + else() + message(WARNING "Could not find Qt in Maya devkit directory: ${MAYA_DEVKIT_LOCATION}. Building with Qt features will be disabled.") + endif() + endif() endif() #------------------------------------------------------------------------------ diff --git a/build.py b/build.py index ad76699b12..459620653e 100755 --- a/build.py +++ b/build.py @@ -2,6 +2,7 @@ from __future__ import print_function from distutils.spawn import find_executable +from glob import glob import argparse import contextlib @@ -18,6 +19,8 @@ import sys import distutils.util import time +import zipfile +import tarfile ############################################################ # Helpers for printing output @@ -359,6 +362,99 @@ def RunMakeZipArchive(context): PrintError("Failed to write to directory {pkgDir} : {exp}".format(pkgDir=pkgDir,exp=exp)) sys.exit(1) +def SetupMayaQt(context): + def haveQtHeaders(rootPath): + if os.path.exists(rootPath): + # MayaUsd uses these components from Qt (so at a minimum we must find them). + qtComponentsToFind = ['QtCore', 'QtGui', 'QtWidgets'] + # Qt6 includes the entire Qt in a single zip file, which when extracted ends in folder 'Qt'. + startDir = os.path.join(rootPath, 'Qt', 'include') if os.path.exists(os.path.join(rootPath, 'Qt')) else os.path.join(rootPath, 'include') + for root,dirs,files in os.walk(startDir): + if 'qt' not in root.lower() or not files: + continue + if not any(root.endswith(qtComp) for qtComp in qtComponentsToFind): + # Skip any folders that aren't the components we are looking for. + continue + + for qtComp in qtComponentsToFind[:]: # Loop over slice copy as we remove items + if qtComp in root and '{comp}version.h'.format(comp=qtComp.lower()) in files: + qtComponentsToFind.remove(qtComp) + PrintInfo('Found {comp} in {dir}'.format(comp=qtComp, dir=root)) + break # Once we've found (and removed) a component, move to the next os.walk + + if not qtComponentsToFind: # Once we've found them all, we are done. + return True + + # The list of directories (in order) that we'll search. This list matches the one + # in FindMayaQt.cmake. + dirsToSearch = [context.devkitLocation] + if 'MAYA_DEVKIT_LOCATION' in os.environ: + dirsToSearch.append(os.path.expandvars('$MAYA_DEVKIT_LOCATION')) + dirsToSearch.append(context.mayaLocation) + if 'MAYA_LOCATION' in os.environ: + dirsToSearch.append(os.path.expandvars('$MAYA_LOCATION')) + + # Check if the Qt zip file has been extracted (we need the Qt headers). + for dirToSearch in dirsToSearch: + if haveQtHeaders(dirToSearch): + PrintStatus('Found Maya Qt headers in: {dir}'.format(dir=dirToSearch)) + return + + # Qt5 + # Didn't find Qt headers, so try and extract qt_5.x-include.zip (the first one we find). + qtIncludeArchiveName = 'qt_5*-include.zip' if Windows() else 'qt_5*-include.tar.gz' + for dirToSearch in dirsToSearch: + qtZipFiles = glob(os.path.join(dirToSearch, 'include', qtIncludeArchiveName)) + if qtZipFiles: + qtZipFile = qtZipFiles[0] + baseDir = os.path.dirname(qtZipFile) + if os.access(baseDir, os.W_OK): + qtZipDirName = os.path.basename(qtZipFile) + qtZipDirName = qtZipDirName.replace('.zip', '').replace('.tar.gz', '') + qtZipDirFolder = os.path.join(baseDir, qtZipDirName) + PrintStatus("Could not find Maya Qt headers.") + PrintStatus(" Extracting '{zip}' to '{dir}'".format(zip=qtZipFile, dir=qtZipDirFolder)) + if not os.path.exists(qtZipDirFolder): + os.makedirs(os.path.join(baseDir, qtZipDirFolder)) + try: + # We only need certain Qt components so we only extract the headers for those. + if Windows(): + archive = zipfile.ZipFile(qtZipFile, mode='r') + files = [n for n in archive.namelist() + if (n.startswith('QtCore/') or n.startswith('QtGui/') or n.startswith('QtWidgets/'))] + else: + archive = tarfile.open(qtZipFile, mode='r') + files = [n for n in archive.getmembers() + if (n.name.startswith('./QtCore/') or n.name.startswith('./QtGui/') or n.name.startswith('./QtWidgets/'))] + archive.extractall(qtZipDirFolder, files) + archive.close() + except zipfile.BadZipfile as error: + PrintError(str(error)) + except tarfile.TarError as error: + PrintError(str(error)) + + # We found and extracted the Qt5 include zip - we are done. + return + + # Qt6 + # The entire Qt is in a single zip file, which we extract to 'Qt'. + # Then we can simply use find_package(Qt6) on it. + for dirToSearch in dirsToSearch: + # Qt archive has same name on all platforms. + qtArchive = os.path.join(dirToSearch, 'Qt.tar.gz') + if os.path.exists(qtArchive): + qtZipDirFolder = os.path.dirname(qtArchive) + if os.access(qtZipDirFolder, os.W_OK): + PrintStatus("Could not find Maya Qt6.") + PrintStatus(" Extracting '{zip}' to '{dir}'".format(zip=qtArchive, dir=qtZipDirFolder)) + try: + archive = tarfile.open(qtArchive, mode='r') + archive.extractall(qtZipDirFolder) + archive.close() + except tarfile.TarError as error: + PrintError(str(error)) + return + def BuildAndInstall(context, buildArgs, stages): with CurrentWorkingDirectory(context.mayaUsdSrcDir): extraArgs = [] @@ -389,10 +485,6 @@ def BuildAndInstall(context, buildArgs, stages): else: extraArgs.append('-DMAYAUSD_DEFINE_BOOST_DEBUG_PYTHON_FLAG=OFF') - if context.qtLocation: - extraArgs.append('-DQT_LOCATION="{qtLocation}"' - .format(qtLocation=context.qtLocation)) - extraArgs += buildArgs stagesArgs += stages @@ -477,7 +569,7 @@ def Package(context): help="Define Boost Python Debug if your Python library comes with Debugging symbols (default: %(default)s).") parser.add_argument("--qt-location", type=str, - help="Directory where Qt is installed.") + help="DEPRECATED: Qt is found automatically in Maya devkit.") parser.add_argument("--build-args", type=str, nargs="*", default=[], help=("Comma-separated list of arguments passed into CMake when building libraries")) @@ -486,7 +578,7 @@ def Package(context): help=("Comma-separated list of arguments passed into CTest.(e.g -VV, --output-on-failure)")) parser.add_argument("--stages", type=str, nargs="*", default=['clean','configure','build','install'], - help=("Comma-separated list of stages to execute.(possible stages: clean, configure, build, install, test, package)")) + help=("Comma-separated list of stages to execute. Possible stages: clean, configure, build, install, test, package.")) parser.add_argument("-j", "--jobs", type=int, default=GetCPUCount(), help=("Number of build jobs to run in parallel. " @@ -550,9 +642,11 @@ def __init__(self, args): self.devkitLocation = (os.path.abspath(args.devkit_location) if args.devkit_location else None) - # Qt Location - self.qtLocation = (os.path.abspath(args.qt_location) - if args.qt_location else None) + # DEPRECATED: Qt Location + if args.qt_location: + PrintError("--qt-location flag is deprecated as Qt is found automatically in Maya devkit.") + PrintError("If you want to use custom Qt, use flag: --build-args=-DQT_LOCATION=") + sys.exit(1) # MaterialX self.materialxEnabled = args.build_materialx @@ -632,6 +726,10 @@ def __init__(self, args): Print(summaryMsg) + # Make sure Qt from Maya devkit is ready. + if 'configure' in context.stagesArgs: + SetupMayaQt(context) + # BuildAndInstall if any(stage in ['clean', 'configure', 'build', 'install'] for stage in context.stagesArgs): StartBuild() diff --git a/cmake/modules/FindMaya_Qt.cmake b/cmake/modules/FindMaya_Qt.cmake new file mode 100644 index 0000000000..5d9ca405fb --- /dev/null +++ b/cmake/modules/FindMaya_Qt.cmake @@ -0,0 +1,190 @@ +# Module to find Qt in Maya devkit. +# +# This module searches for the necessary Qt files in the Maya devkit. +# +# It relies on Maya being found first: find_package(Maya) +# as it uses variables set there. +# +# Variables that will be defined: +# Maya_Qt_FOUND Defined if Qt has been found +# Qt{$QT_MAJOR_VERSION}_FOUND Defined if Qt has been found +# MAYA_QT_INC_DIR The include folder where Qt header files can be found +# QT_MOC_EXECUTABLE Path to Qt moc executable +# QT_UIC_EXECUTABLE Path to Qt uic executable +# QT_RCC_EXECUTABLE Path to Qt rcc executable +# MAYA_QT_VERSION The version of Qt in the Maya devkit +# + +# List of directories to search for Maya Qt headers/libraries/executables. +set(MAYA_DIRS_TO_SEARCH + ${MAYA_DEVKIT_LOCATION} + $ENV{MAYA_DEVKIT_LOCATION} + ${MAYA_LOCATION} + $ENV{MAYA_LOCATION} +) + +# Find the directory paths to the Qt headers (using the input components list). +foreach(MAYA_DIR_TO_SEARCH ${MAYA_DIRS_TO_SEARCH}) + file(TO_CMAKE_PATH ${MAYA_DIR_TO_SEARCH} qt_root_dir) + + # Make sure we can find the header files for each Qt component. + set(Maya_Qt_Headers_FOUND TRUE) # Assume we will find them all. + foreach(Maya_Qt_Component ${Maya_Qt_FIND_COMPONENTS}) + string(TOUPPER ${Maya_Qt_Component} MAYA_QT_COMPONENT) + string(TOLOWER ${Maya_Qt_Component} maya_qt_component) + unset(MAYA_QT_VERSION_FILE) + file(GLOB_RECURSE MAYA_QT_VERSION_FILE "${qt_root_dir}/include/*/qt${maya_qt_component}version.h") + if (NOT MAYA_QT_VERSION_FILE) + set(Maya_Qt_Headers_FOUND FALSE) + break() # We could not find the headers in this search path (try the next). + endif() + # Get the include directory for this component. + get_filename_component(MAYA_QT_${MAYA_QT_COMPONENT}_DIR ${MAYA_QT_VERSION_FILE} DIRECTORY) + endforeach() + + if (Maya_Qt_Headers_FOUND AND MAYA_QT_${MAYA_QT_COMPONENT}_DIR) + # Get the base Qt include path (we can simply use the last component found as they are + # all in the same base path). + get_filename_component(MAYA_QT_INC_DIR ${MAYA_QT_${MAYA_QT_COMPONENT}_DIR} DIRECTORY) + break() # We found all the headers in this search path. + endif() +endforeach() + +# Find Qt utilities. +set(Maya_Qt_Utilities "moc;uic;rcc") +foreach(Maya_Qt_Utility ${Maya_Qt_Utilities}) + string(TOUPPER ${Maya_Qt_Utility} MAYA_QT_UTILITY) + find_program(QT_${MAYA_QT_UTILITY}_EXECUTABLE + NAMES + ${Maya_Qt_Utility} + PATHS + ${MAYA_DEVKIT_LOCATION} + $ENV{MAYA_DEVKIT_LOCATION} + ${MAYA_LOCATION} + $ENV{MAYA_LOCATION} + ${MAYA_BASE_DIR} + PATH_SUFFIXES + devkit/bin + bin + NO_DEFAULT_PATH + DOC + "Maya Qt utility" + ) +endforeach() + +if (QT_UIC_EXECUTABLE) + # Get the Qt version based on UIC + execute_process(COMMAND ${QT_UIC_EXECUTABLE} "--version" OUTPUT_VARIABLE QT_UIC_VERSION) + STRING(REPLACE "." " " QT_UIC_VERSION ${QT_UIC_VERSION}) + STRING(REPLACE " " ";" QT_UIC_VERSION ${QT_UIC_VERSION}) + LIST(GET QT_UIC_VERSION 1 QT_VERSION_MAJOR) + LIST(GET QT_UIC_VERSION 2 QT_VERSION_MINOR) + LIST(GET QT_UIC_VERSION 3 QT_PATCH_LEVEL) + set(MAYA_QT_VERSION ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_PATCH_LEVEL}) + add_executable(Qt${QT_VERSION_MAJOR}::uic IMPORTED GLOBAL) + set_target_properties(Qt${QT_VERSION_MAJOR}::uic PROPERTIES IMPORTED_LOCATION ${QT_UIC_EXECUTABLE}) +endif() + +if (QT_MOC_EXECUTABLE) + add_executable(Qt${QT_VERSION_MAJOR}::moc IMPORTED GLOBAL) + set_target_properties(Qt${QT_VERSION_MAJOR}::moc PROPERTIES IMPORTED_LOCATION ${QT_MOC_EXECUTABLE}) +endif() + +if (QT_RCC_EXECUTABLE) + add_executable(Qt${QT_VERSION_MAJOR}::rcc IMPORTED GLOBAL) + set_target_properties(Qt${QT_VERSION_MAJOR}::rcc PROPERTIES IMPORTED_LOCATION ${QT_RCC_EXECUTABLE}) +endif() + +# Find the Maya Qt libraries (using the input components list). +foreach(Maya_Qt_LIB ${Maya_Qt_FIND_COMPONENTS}) + string(TOUPPER ${Maya_Qt_LIB} MAYA_QT_LIB) + set(QT_NAME_TO_FIND "Qt${QT_VERSION_MAJOR}${Maya_Qt_LIB}") + if (CMAKE_BUILD_TYPE MATCHES "Debug") + # When in debug we sometimes need to modify the names. + if (IS_WINDOWS) + string(APPEND QT_NAME_TO_FIND "d") # extra 'd' at end + elseif (IS_MACOSX) + string(APPEND QT_NAME_TO_FIND "_debug") # extra '_debug' at end + endif() + endif() + find_library(MAYA_QT${MAYA_QT_LIB}_LIBRARY + NAMES + ${QT_NAME_TO_FIND} + PATHS + ${MAYA_LIBRARY_DIR} + NO_DEFAULT_PATH + DOC + "Maya Qt library" + ) + if (MAYA_QT${MAYA_QT_LIB}_LIBRARY) + # Add each Qt library and set the target properties. + set(__target_name "Qt${QT_VERSION_MAJOR}::${Maya_Qt_LIB}") + add_library(${__target_name} SHARED IMPORTED GLOBAL) + set_target_properties(${__target_name} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${MAYA_QT_${MAYA_QT_LIB}_DIR};${MAYA_QT_INC_DIR}") + set_target_properties(${__target_name} PROPERTIES IMPORTED_CONFIGURATIONS RELEASE) # RELEASE? + set_target_properties(${__target_name} PROPERTIES IMPORTED_LOCATION "${MAYA_QT${MAYA_QT_LIB}_LIBRARY}") + if(IS_WINDOWS) + set_target_properties(${__target_name} PROPERTIES IMPORTED_IMPLIB "${MAYA_QT${MAYA_QT_LIB}_LIBRARY}") + set_target_properties(${__target_name} PROPERTIES INTERFACE_LINK_LIBRARIES "${__target_name}") + endif() + if(${QT_VERSION_MAJOR} VERSION_EQUAL 6) + # Add extra compile options required for Qt6. + set_target_properties(${__target_name} PROPERTIES + INTERFACE_COMPILE_FEATURES "cxx_std_17") # Comes from Qt6::Platform + if (IS_WINDOWS) + # Comes from Qt6::Platform + set_target_properties(${__target_name} PROPERTIES + INTERFACE_COMPILE_OPTIONS "\$<\$:/Zc:__cplusplus;/permissive->") + endif() + endif() + + # Add a versionless target. + set(__versionless_target_name "Qt::${Maya_Qt_LIB}") + if (TARGET ${__target_name} AND NOT TARGET ${__versionless_target_name}) + add_library(${__versionless_target_name} INTERFACE IMPORTED) + set_target_properties(${__versionless_target_name} PROPERTIES INTERFACE_LINK_LIBRARIES ${__target_name}) + endif() + endif() +endforeach() + +# Set the Qt library dependencies (special cases we know about). +if (IS_LINUX AND TARGET Qt${QT_VERSION_MAJOR}::Core) + set_property(TARGET Qt${QT_VERSION_MAJOR}::Core APPEND PROPERTY INTERFACE_COMPILE_OPTIONS -fPIC) + set_property(TARGET Qt${QT_VERSION_MAJOR}::Core PROPERTY INTERFACE_COMPILE_FEATURES cxx_decltype) +endif() +if (TARGET Qt${QT_VERSION_MAJOR}::Widgets) + set_target_properties(Qt${QT_VERSION_MAJOR}::Widgets PROPERTIES INTERFACE_LINK_LIBRARIES "Qt${QT_VERSION_MAJOR}::Gui;Qt${QT_VERSION_MAJOR}::Core") +endif() +if (TARGET Qt${QT_VERSION_MAJOR}::Gui) + set_target_properties(Qt${QT_VERSION_MAJOR}::Gui PROPERTIES INTERFACE_LINK_LIBRARIES "Qt${QT_VERSION_MAJOR}::Core") +endif() + +# Handle the QUIETLY and REQUIRED arguments and set UFE_FOUND to TRUE if +# all listed variables are TRUE. +include(FindPackageHandleStandardArgs) + +list(APPEND Maya_QtRequiredVars "MAYA_QT_INC_DIR" "QT_MOC_EXECUTABLE" "QT_UIC_EXECUTABLE" "QT_RCC_EXECUTABLE") +foreach(Maya_Qt_LIB ${Maya_Qt_FIND_COMPONENTS}) + string(TOUPPER ${Maya_Qt_LIB} MAYA_QT_LIB) + list(APPEND Maya_QtRequiredVars "MAYA_QT${MAYA_QT_LIB}_LIBRARY") +endforeach() + +find_package_handle_standard_args(Maya_Qt + REQUIRED_VARS + ${Maya_QtRequiredVars} + VERSION_VAR + MAYA_QT_VERSION +) + +if (Maya_Qt_FOUND) + set(Qt${QT_VERSION_MAJOR}_FOUND TRUE) + message(STATUS "Using Maya Qt version ${MAYA_QT_VERSION}") + message(STATUS " Qt include path : ${MAYA_QT_INC_DIR}") + message(STATUS " moc executable : ${QT_MOC_EXECUTABLE}") + message(STATUS " uic executable : ${QT_UIC_EXECUTABLE}") + message(STATUS " rcc executable : ${QT_RCC_EXECUTABLE}") + foreach(Maya_Qt_LIB ${Maya_Qt_FIND_COMPONENTS}) + string(TOUPPER ${Maya_Qt_LIB} MAYA_QT_LIB) + message(STATUS " Qt ${Maya_Qt_LIB} library: ${MAYA_QT${MAYA_QT_LIB}_LIBRARY}") + endforeach() +endif() diff --git a/lib/mayaUsd/CMakeLists.txt b/lib/mayaUsd/CMakeLists.txt index 4da2c1ad2d..89528eae3b 100644 --- a/lib/mayaUsd/CMakeLists.txt +++ b/lib/mayaUsd/CMakeLists.txt @@ -49,7 +49,7 @@ target_compile_definitions(${PROJECT_NAME} $<$:GL_GLEXT_PROTOTYPES> $<$:GLX_GLXEXT_PROTOTYPES> $<$:WANT_MATERIALX_BUILD> - $<$,$>:WANT_QT_BUILD> + $<$:WANT_QT_BUILD> $<$:BUILD_HDMAYA> # this flag is needed when building maya-usd in Maya diff --git a/lib/usd/CMakeLists.txt b/lib/usd/CMakeLists.txt index e87bbbf200..6d1234f993 100644 --- a/lib/usd/CMakeLists.txt +++ b/lib/usd/CMakeLists.txt @@ -23,7 +23,7 @@ add_subdirectory(schemas) add_subdirectory(utils) -if(Qt5_FOUND OR Qt6_FOUND) +if(Qt_FOUND) add_subdirectory(ui) endif() diff --git a/lib/usd/ui/CMakeLists.txt b/lib/usd/ui/CMakeLists.txt index cd38e09381..302a4be6b3 100644 --- a/lib/usd/ui/CMakeLists.txt +++ b/lib/usd/ui/CMakeLists.txt @@ -47,11 +47,7 @@ target_compile_definitions(${PROJECT_NAME} # QT_NO_KEYWORDS prevents Qt from defining the foreach, signals, slots and emit macros. # this avoids overlap between Qt macros and boost, and enforces using Q_ macros. -if (Qt6_FOUND) - set_target_properties(Qt6::Core PROPERTIES INTERFACE_COMPILE_DEFINITIONS QT_NO_KEYWORDS) -else() - set_target_properties(Qt5::Core PROPERTIES INTERFACE_COMPILE_DEFINITIONS QT_NO_KEYWORDS) -endif() +set_target_properties(Qt::Core PROPERTIES INTERFACE_COMPILE_DEFINITIONS QT_NO_KEYWORDS) mayaUsd_compile_config(${PROJECT_NAME}) @@ -62,9 +58,9 @@ target_link_libraries(${PROJECT_NAME} PUBLIC mayaUsd PRIVATE - $,Qt6::Core,Qt5::Core> - $,Qt6::Gui,Qt5::Gui> - $,Qt6::Widgets,Qt5::Widgets> + Qt::Core + Qt::Gui + Qt::Widgets ) # ----------------------------------------------------------------------------- diff --git a/lib/usd/ui/importDialogDemo/CMakeLists.txt b/lib/usd/ui/importDialogDemo/CMakeLists.txt index b0fef08452..302fb16698 100644 --- a/lib/usd/ui/importDialogDemo/CMakeLists.txt +++ b/lib/usd/ui/importDialogDemo/CMakeLists.txt @@ -40,9 +40,9 @@ mayaUsd_compile_config(${PROJECT_NAME}) target_link_libraries(${PROJECT_NAME} PRIVATE mayaUsdUI - $,Qt6::Core,Qt5::Core> - $,Qt6::Gui,Qt5::Gui> - $,Qt6::Widgets,Qt5::Widgets> + Qt::Core + Qt::Gui + Qt::Widgets ) if (IS_LINUX) diff --git a/plugin/adsk/plugin/CMakeLists.txt b/plugin/adsk/plugin/CMakeLists.txt index 657a9ccbf9..86c3c55dfe 100644 --- a/plugin/adsk/plugin/CMakeLists.txt +++ b/plugin/adsk/plugin/CMakeLists.txt @@ -48,7 +48,7 @@ endif() target_compile_definitions(${TARGET_NAME} PRIVATE MAYAUSD_PLUGIN_EXPORT - $<$,$>:WANT_QT_BUILD> + $<$:WANT_QT_BUILD> ) if(DEFINED MAYAUSD_VERSION) @@ -77,7 +77,7 @@ target_link_libraries(${TARGET_NAME} basePxrUsdPreviewSurface ) -if (Qt5_FOUND OR Qt6_FOUND) +if (Qt_FOUND) target_link_libraries(${TARGET_NAME} PRIVATE mayaUsdUI From 22f81d4ae886dcff79c4a766afd11400bcf3b24b Mon Sep 17 00:00:00 2001 From: Sean Donnelly <23455376+seando-adsk@users.noreply.github.com> Date: Mon, 28 Aug 2023 10:38:49 -0400 Subject: [PATCH 2/4] EMSUSD-282 - MayaUsd: build with Qt directly from Maya devkit/runtime * Fix for security/bandit B202: tarfile_unsafe_members --- build.py | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/build.py b/build.py index 459620653e..6921114ed9 100755 --- a/build.py +++ b/build.py @@ -385,6 +385,32 @@ def haveQtHeaders(rootPath): if not qtComponentsToFind: # Once we've found them all, we are done. return True + def safeTarfileExtract(members): + """Use a function to look for bad paths in the tarfile archive to fix + security/bandit B202: tarfile_unsafe_members.""" + + def isBadPath(path, base): + return not os.path.realpath(os.path.abspath(os.path.join(base, path))).startswith(base) + def isBadLink(info, base): + # Links are interpreted relative to the directory containing the link. + tip = os.path.realpath(os.path.abspath(os.path.join(base, dirname(info.name)))) + return _badpath(info.linkname, base=tip) + + base = os.path.realpath(os.path.abspath('.')) + result = [] + for finfo in members: + # If any bad paths for links are found in the tarfile, print an error + # and don't extract anything from tarfile. + if isBadPath(finfo.name, base): + PrintError('Found illegal path {path} in tarfile, blocking tarfile extraction.'.format(path=finfo.name)) + return [] + elif (finfo.issym() or finfo.islnk()) and isBadLink(finfo, base): + PrintError('Found illegal link {link} in tarfile, blocking tarfile extraction.'.format(link=finfo.linkname)) + return [] + else: + result.append(finfo) + return result + # The list of directories (in order) that we'll search. This list matches the one # in FindMayaQt.cmake. dirsToSearch = [context.devkitLocation] @@ -419,15 +445,17 @@ def haveQtHeaders(rootPath): try: # We only need certain Qt components so we only extract the headers for those. if Windows(): - archive = zipfile.ZipFile(qtZipFile, mode='r') - files = [n for n in archive.namelist() + zipArchive = zipfile.ZipFile(qtZipFile, mode='r') + files = [n for n in zipArchive.namelist() if (n.startswith('QtCore/') or n.startswith('QtGui/') or n.startswith('QtWidgets/'))] + zipArchive.extractall(qtZipDirFolder, files) + zipArchive.close() else: - archive = tarfile.open(qtZipFile, mode='r') - files = [n for n in archive.getmembers() + tarArchive = tarfile.open(qtZipFile, mode='r') + files = [n for n in tarArchive.getmembers() if (n.name.startswith('./QtCore/') or n.name.startswith('./QtGui/') or n.name.startswith('./QtWidgets/'))] - archive.extractall(qtZipDirFolder, files) - archive.close() + tarArchive.extractall(qtZipDirFolder, members=safeTarfileExtract(files)) + tarArchive.close() except zipfile.BadZipfile as error: PrintError(str(error)) except tarfile.TarError as error: @@ -449,7 +477,7 @@ def haveQtHeaders(rootPath): PrintStatus(" Extracting '{zip}' to '{dir}'".format(zip=qtArchive, dir=qtZipDirFolder)) try: archive = tarfile.open(qtArchive, mode='r') - archive.extractall(qtZipDirFolder) + archive.extractall(qtZipDirFolder, members=safeTarfileExtract(archive.getmembers())) archive.close() except tarfile.TarError as error: PrintError(str(error)) From 8927d6ea660e8bb3c2c710940e43586b43eaef0c Mon Sep 17 00:00:00 2001 From: Sean Donnelly <23455376+seando-adsk@users.noreply.github.com> Date: Tue, 29 Aug 2023 13:30:29 -0400 Subject: [PATCH 3/4] EMSUSD-282 - MayaUsd: build with Qt directly from Maya devkit/runtime * Code review feedback - remove QT_LOCATION completely and cmake error if Qt cannot be found in Maya devkit. --- CMakeLists.txt | 62 +++++++++++++++++++------------------------------- build.py | 4 +--- 2 files changed, 25 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aecaf3e913..672a48d958 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,8 +37,6 @@ set(BUILD_WITH_PYTHON_3_VERSION 3.7 CACHE STRING "The version of Python 3 to bui option(CMAKE_WANT_MATERIALX_BUILD "Enable building with MaterialX." ON) option(CMAKE_WANT_VALIDATE_UNDO_ITEM "Enable validating undo items list." OFF) -option(BUILD_WITH_QT_6 "Build with QT 6." OFF) - set(PXR_OVERRIDE_PLUGINPATH_NAME PXR_PLUGINPATH_NAME CACHE STRING "Name of env var USD searches to find plugins") @@ -154,51 +152,39 @@ if(TARGET hdMtlx) endif() endif() -if(DEFINED QT_LOCATION) - if(NOT DEFINED QT_VERSION) - if(BUILD_WITH_QT_6) - set(QT_VERSION "6.5") - else() # Maya 2022, 2023, 2024 - set(QT_VERSION "5.15") - endif() - message(STATUS "Setting Qt version to Qt ${QT_VERSION}") - endif() - set(CMAKE_PREFIX_PATH "${QT_LOCATION}") - - if(BUILD_WITH_QT_6) - find_package(Qt6 ${QT_VERSION} COMPONENTS Core Gui Widgets REQUIRED) - else() - find_package(Qt5 ${QT_VERSION} COMPONENTS Core Gui Widgets REQUIRED) - endif() - if(Qt5_FOUND OR Qt6_FOUND) - message(STATUS "Found Qt in custom location, building with Qt ${QT_VERSION} features enabled.") - set(Qt_FOUND TRUE) - else() - message(WARNING "Could not find Qt in custom location: ${QT_LOCATION}. Building with Qt features will be disabled.") - endif() -else() +if(MAYA_APP_VERSION VERSION_GREATER 2024) # First look for Qt6 in the Maya devkit. # The Qt6 archive in the Maya devkit contains everything needed for the normal cmake find_package. set(CMAKE_PREFIX_PATH "${MAYA_DEVKIT_LOCATION}/Qt") find_package(Qt6 6.5 COMPONENTS Core Gui Widgets QUIET) - if (Qt6_FOUND) - message(STATUS "Found Qt6 in Maya devkit, building with Qt ${Qt6_VERSION} features enabled.") - set(BUILD_WITH_QT_6 ON) +endif() +if (Qt6_FOUND) + message(STATUS "Found Qt6 in Maya devkit, building with Qt ${Qt6_VERSION} features enabled.") + set(Qt_FOUND TRUE) +else() + # If we didn't find Qt6 in Maya devkit, search again, but for Qt5 this time. + # Minimum version required is 5.15 (Maya 2022/2023/2024). + # This will find Qt in the Maya devkit using a custom find package. + # So the version will match Maya we are building for. + find_package(Maya_Qt 5.15 COMPONENTS Core Gui Widgets QUIET) + if (Maya_Qt_FOUND) + message(STATUS "Found Qt5 in Maya devkit, building with Qt ${MAYA_QT_VERSION} features enabled.") set(Qt_FOUND TRUE) + endif() +endif() + +if(NOT Qt_FOUND) + message(SEND_ERROR "Could not find Qt in Maya devkit directory: ${MAYA_DEVKIT_LOCATION}.") + if(MAYA_APP_VERSION VERSION_GREATER 2024) + message(STATUS " You must extract Qt.tar.gz") else() - # If we didn't find Qt6 in Maya devkit, search again, but for Qt5 this time. - # Minimum version required is 5.15 (Maya 2022/2023/2024). - # This will find Qt in the Maya devkit using a custom find package. - # So the version will match Maya we are building for. - find_package(Maya_Qt 5.15 COMPONENTS Core Gui Widgets QUIET) - if (Maya_Qt_FOUND) - message(STATUS "Found Qt5 in Maya devkit, building with Qt ${MAYA_QT_VERSION} features enabled.") - set(BUILD_WITH_QT_6 OFF) - set(Qt_FOUND TRUE) + if (IS_WINDOWS) + message(STATUS " In Maya devkit you must extract include/qt_5.15.2_vc14-include.zip") else() - message(WARNING "Could not find Qt in Maya devkit directory: ${MAYA_DEVKIT_LOCATION}. Building with Qt features will be disabled.") + message(STATUS " In Maya devkit you must extract include/qt_5.15.2-include.tar.gz") endif() endif() + message(FATAL_ERROR "Cannot build MayaUsd without Qt.") endif() #------------------------------------------------------------------------------ diff --git a/build.py b/build.py index 6921114ed9..668c912176 100755 --- a/build.py +++ b/build.py @@ -672,9 +672,7 @@ def __init__(self, args): # DEPRECATED: Qt Location if args.qt_location: - PrintError("--qt-location flag is deprecated as Qt is found automatically in Maya devkit.") - PrintError("If you want to use custom Qt, use flag: --build-args=-DQT_LOCATION=") - sys.exit(1) + PrintWarning("--qt-location flag is deprecated as Qt is found automatically in Maya devkit.") # MaterialX self.materialxEnabled = args.build_materialx From 547740d32ff19de71233aa37c276740fb8fe62ef Mon Sep 17 00:00:00 2001 From: Sean Donnelly <23455376+seando-adsk@users.noreply.github.com> Date: Wed, 30 Aug 2023 08:44:01 -0400 Subject: [PATCH 4/4] EMSUSD-282 - MayaUsd: build with Qt directly from Maya devkit/runtime * Fixed python scripting error. --- build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index 668c912176..a5f4aac049 100755 --- a/build.py +++ b/build.py @@ -393,8 +393,8 @@ def isBadPath(path, base): return not os.path.realpath(os.path.abspath(os.path.join(base, path))).startswith(base) def isBadLink(info, base): # Links are interpreted relative to the directory containing the link. - tip = os.path.realpath(os.path.abspath(os.path.join(base, dirname(info.name)))) - return _badpath(info.linkname, base=tip) + tip = os.path.realpath(os.path.abspath(os.path.join(base, os.path.dirname(info.name)))) + return isBadPath(info.linkname, base=tip) base = os.path.realpath(os.path.abspath('.')) result = []