-
Notifications
You must be signed in to change notification settings - Fork 202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EMSUSD-282 - MayaUsd: build with Qt directly from Maya devkit/runtime #3298
Changes from all commits
4805a7c
22f81d4
8927d6e
547740d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,29 +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") | ||
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) | ||
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 (IS_WINDOWS) | ||
message(STATUS " In Maya devkit you must extract include/qt_5.15.2_vc14-include.zip") | ||
else() | ||
message(STATUS " In Maya devkit you must extract include/qt_5.15.2-include.tar.gz") | ||
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) | ||
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() | ||
else() | ||
message(STATUS "QT_LOCATION not set. Building Qt features will be disabled.") | ||
message(FATAL_ERROR "Cannot build MayaUsd without Qt.") | ||
Comment on lines
+176
to
+187
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As per discussion with Dhruv if Qt cannot be found in Maya devkit, output a cmake error (stopping build). Also removed the QT_LOCATION flag as there is no need to build with a custom Qt anymore as we can always build with Qt from Maya devkit. |
||
endif() | ||
|
||
#------------------------------------------------------------------------------ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,127 @@ 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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Helper function to search (recursively) to find the qt headers (only the 3 components we use). If found then we know the qt zip was extracted. |
||
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 | ||
|
||
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, os.path.dirname(info.name)))) | ||
return isBadPath(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] | ||
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')) | ||
Comment on lines
+416
to
+421
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Various places Qt could be found, but it will most likely always be found in the first entry which is the Maya devkit 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 | ||
Comment on lines
+423
to
+427
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Search thru all the locations and try to find the Qt headers, if we do return - no extra step needed. |
||
|
||
# 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' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For Qt5 we only need to extract the headers. |
||
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(): | ||
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: | ||
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/'))] | ||
tarArchive.extractall(qtZipDirFolder, members=safeTarfileExtract(files)) | ||
tarArchive.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') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For Qt6 extract the entire archive. This one is new and contains everything needed to build with Qt. |
||
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, members=safeTarfileExtract(archive.getmembers())) | ||
archive.close() | ||
except tarfile.TarError as error: | ||
PrintError(str(error)) | ||
return | ||
|
||
def BuildAndInstall(context, buildArgs, stages): | ||
with CurrentWorkingDirectory(context.mayaUsdSrcDir): | ||
extraArgs = [] | ||
|
@@ -389,10 +513,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 +597,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 +606,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 +670,9 @@ 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: | ||
PrintWarning("--qt-location flag is deprecated as Qt is found automatically in Maya devkit.") | ||
|
||
# MaterialX | ||
self.materialxEnabled = args.build_materialx | ||
|
@@ -632,6 +752,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() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Maya devkit has been "fixed" for the switch to Qt6 by including a Qt zip that has all of Qt packaged up for use by cmake builds. All we need to do now is set the CMAKE_PREFIX_PATH and then call the normal find_package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use QUIET here since this find is allowed to fail, meaning there is no Qt6 and we'll fall below and search for Qt5.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also relies on the new
Qt.tar.gz
file being extracted this I do inbuild.py
.