diff --git a/README.md b/README.md index 6f8d6f42a2..8c59a2dbce 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,11 @@ then build and install USD into `/path/to/my_usd_install_dir`. > python USD/build_scripts/build_usd.py /path/to/my_usd_install_dir ``` +Additionally you can provide the `--build-apple-framework` flag to create a framework output, for easier integration +into an Xcode application. Framework support is currently experimental. + +It is recommended to build frameworks as a monolith with `--build-monolithic`. + ###### iOS When building from a macOS system, you can cross compile for iOS based platforms. @@ -150,6 +155,7 @@ Additionally, they will not support Python bindings or command line tools. To build for iOS, add the `--build-target iOS` parameter. +iOS builds default to building as a framework. ##### Windows: diff --git a/build_scripts/apple_utils.py b/build_scripts/apple_utils.py index 7224ec1c7d..ac58c9e353 100644 --- a/build_scripts/apple_utils.py +++ b/build_scripts/apple_utils.py @@ -217,8 +217,9 @@ def GetDevelopmentTeamID(): except Exception as ex: raise Exception("No development team found with exception " + ex) -def CodesignFiles(files): - codeSignID = GetCodeSignID() +def CodesignFiles(files, codeSignID=None): + if not codeSignID: + codeSignID = GetCodeSignID() for f in files: subprocess.call(['codesign', '-f', '-s', '{codesignid}' @@ -226,16 +227,17 @@ def CodesignFiles(files): stdout=devout, stderr=devout) -def Codesign(install_path, verbose_output=False): +def Codesign(context, verbose_output=False): if not MacOS(): return False if verbose_output: global devout devout = sys.stdout - files = ExtractFilesRecursive(install_path, + files = ExtractFilesRecursive(context.usdInstDir, (lambda file: '.so' in file or '.dylib' in file)) - CodesignFiles(files) + CodesignFiles(files, context.macOSCodesign) + def CreateUniversalBinaries(context, libNames, x86Dir, armDir): if not MacOS(): @@ -281,4 +283,5 @@ def ConfigureCMakeExtraArgs(context, args:List[str]) -> List[str]: if system_name: args.append(f"-DCMAKE_SYSTEM_NAME={system_name}") - return args \ No newline at end of file + + return args diff --git a/build_scripts/build_usd.py b/build_scripts/build_usd.py index a90f39d3c2..33a36bfcf6 100644 --- a/build_scripts/build_usd.py +++ b/build_scripts/build_usd.py @@ -1789,6 +1789,11 @@ def InstallUSD(context, force, buildArgs): '-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH', '-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH']) + if MacOS(): + extraArgs.append(f"-DPXR_BUILD_APPLE_FRAMEWORK={'ON' if context.buildAppleFramework else 'OFF'}") + if context.macOSCodesign: + extraArgs.append(f"-DPXR_APPLE_CODESIGN_IDENTITY={context.macOSCodesign}") + # Make sure to use boost installed by the build script and not any # system installed boost extraArgs.append('-DBoost_NO_BOOST_CMAKE=On') @@ -1903,6 +1908,12 @@ def InstallUSD(context, force, buildArgs): help=("Build target for macOS cross compilation. " "(default: {})".format( apple_utils.GetBuildTargetDefault()))) + subgroup = group.add_mutually_exclusive_group() + subgroup.add_argument("--build-apple-framework", dest="build_apple_framework", action="store_true", + help="Build USD as an Apple Framework (Default if using build)") + subgroup.add_argument("--no-build-apple-framework", dest="no_build_apple_framework", action="store_true", + help="Do not build USD as an Apple Framework (Default if macOS)") + if apple_utils.IsHostArm(): # Intel Homebrew stores packages in /usr/local which unfortunately can # be where a lot of other things are too. So we only add this flag on arm macs. @@ -1938,6 +1949,7 @@ def InstallUSD(context, force, buildArgs): default=codesignDefault, action="store_true", help=("Enable code signing for macOS builds " "(defaults to enabled on Apple Silicon)")) + group.add_argument("--codesign-id", dest="macos_codesign_id", type=str) if Linux(): group.add_argument("--use-cxx11-abi", type=int, choices=[0, 1], @@ -2216,17 +2228,22 @@ def __init__(self, args): self.ignorePaths = args.ignore_paths or [] self.buildTarget = None self.macOSCodesign = "" + self.buildAppleFramework = False # Build target and code signing if MacOS(): self.buildTarget = args.build_target apple_utils.SetTarget(self, self.buildTarget) - self.macOSCodesign = \ - (args.macos_codesign if hasattr(args, "macos_codesign") - else False) + if args.macos_codesign: + self.macOSCodesign = args.macos_codesign_id or apple_utils.GetCodeSignID() if apple_utils.IsHostArm() and args.ignore_homebrew: self.ignorePaths.append("/opt/homebrew") + self.buildAppleFramework = ((args.build_apple_framework + or self.buildTarget in apple_utils.EMBEDDED_PLATFORMS) + and not args.no_build_apple_framework) + + coreOnly = self.buildTarget in apple_utils.EMBEDDED_PLATFORMS self.useCXX11ABI = \ @@ -2239,10 +2256,10 @@ def __init__(self, args): # Optional components self.buildTests = args.build_tests - self.buildPython = args.build_python and not coreOnly + self.buildPython = args.build_python and not coreOnly and not self.buildAppleFramework self.buildExamples = args.build_examples self.buildTutorials = args.build_tutorials - self.buildTools = args.build_tools and not coreOnly + self.buildTools = args.build_tools and not coreOnly and not self.buildAppleFramework # - Documentation self.buildDocs = args.build_docs or args.build_python_docs @@ -2675,8 +2692,9 @@ def FormatBuildArguments(buildArgs): ]) if MacOS(): - if context.macOSCodesign: - apple_utils.Codesign(context.usdInstDir, verbosity > 1) + # We don't need to codesign when building a framework because it's handled during framework creation + if context.macOSCodesign and not context.buildAppleFramework: + apple_utils.Codesign(context, verbosity > 1) printInstructions = any([context.buildPython, context.buildTools, context.buildPrman]) if printInstructions: @@ -2699,3 +2717,12 @@ def FormatBuildArguments(buildArgs): if context.buildPrman: Print("See documentation at http://openusd.org/docs/RenderMan-USD-Imaging-Plugin.html " "for setting up the RenderMan plugin.\n") + +if context.buildAppleFramework: + Print(""" + Added the following framework to your Xcode Project, (recommended as Embed Without Signing): + OpenUSD.framework + + Set the following compiler argument, to find the headers: + SYSTEM_HEADER_SEARCH_PATHS=$(SRCROOT)/$(TARGET_NAME)/OpenUSD.framework/Headers + """) \ No newline at end of file diff --git a/cmake/defaults/CXXDefaults.cmake b/cmake/defaults/CXXDefaults.cmake index c59d70c8b7..853a6a27f9 100644 --- a/cmake/defaults/CXXDefaults.cmake +++ b/cmake/defaults/CXXDefaults.cmake @@ -106,3 +106,8 @@ if (PXR_PREFER_SAFETY_OVER_SPEED) else() set(PXR_PREFER_SAFETY_OVER_SPEED "0") endif() + +# Set that Apple Framework is being build +if (PXR_BUILD_APPLE_FRAMEWORK) + _add_define("PXR_BUILD_APPLE_FRAMEWORK") +endif() \ No newline at end of file diff --git a/cmake/defaults/Options.cmake b/cmake/defaults/Options.cmake index 1c4e3f562f..4ae5cfba11 100644 --- a/cmake/defaults/Options.cmake +++ b/cmake/defaults/Options.cmake @@ -55,6 +55,7 @@ option(PXR_PREFER_SAFETY_OVER_SPEED ON) if(APPLE) + set(PXR_APPLE_CODESIGN_IDENTITY "-" CACHE STRING "The Codesigning identity needed to sign compiled objects") # Cross Compilation detection as defined in CMake docs # Required to be handled here so it can configure options later on # https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-ios-tvos-visionos-or-watchos @@ -74,6 +75,16 @@ if(APPLE) set(PXR_BUILD_IMAGING OFF) endif () endif () + + option(PXR_BUILD_APPLE_FRAMEWORK "Builds an Apple Framework." APPLE_EMBEDDED) + set(PXR_APPLE_FRAMEWORK_NAME "OpenUSD" CACHE STRING "Name to provide Apple Framework build") + set(PXR_APPLE_IDENTIFIER_DOMAIN "org.openusd" CACHE STRING "Name to provide Apple Framework build") + if (${PXR_BUILD_APPLE_FRAMEWORK}) + if(${PXR_BUILD_USD_TOOLS}) + MESSAGE(STATUS "Setting PXR_BUILD_USD_TOOLS=OFF because PXR_BUILD_APPLE_FRAMEWORK is enabled.") + set(PXR_BUILD_USD_TOOLS OFF) + endif() + endif() endif() diff --git a/cmake/macros/Public.cmake b/cmake/macros/Public.cmake index 97263d4cd1..80ed041d68 100644 --- a/cmake/macros/Public.cmake +++ b/cmake/macros/Public.cmake @@ -1117,6 +1117,12 @@ function(pxr_toplevel_epilogue) # Setup the plugins in the top epilogue to ensure that everybody has had a # chance to update PXR_EXTRA_PLUGINS with their plugin paths. pxr_setup_plugins() + + # Build + if (PXR_BUILD_APPLE_FRAMEWORK) + pxr_create_apple_framework() + endif () + endfunction() # pxr_toplevel_epilogue function(pxr_monolithic_epilogue) @@ -1305,3 +1311,26 @@ function(pxr_build_python_documentation) ") endfunction() # pxr_build_python_documentation + +function(pxr_create_apple_framework) + # CMake can have a lot of different boolean representations, that need to be narrowed down + if (APPLE_EMBEDDED) + set(EMBEDDED_BUILD "true") + else() + set(EMBEDDED_BUILD "false") + endif() + + _get_library_prefix(LIB_PREFIX) + if(TARGET usd_ms) + set(FRAMEWORK_ROOT_LIBRARY_NAME "${LIB_PREFIX}usd_ms.dylib") + else() + set(FRAMEWORK_ROOT_LIBRARY_NAME "${LIB_PREFIX}usd.dylib") + endif() + + # Install the Info.plist and shell script + configure_file(cmake/resources/Info.plist.in "${PROJECT_BINARY_DIR}/Info.plist" @ONLY) + configure_file(cmake/resources/AppleFrameworkBuild.zsh.in "${PROJECT_BINARY_DIR}/AppleFrameworkBuild.zsh" @ONLY) + + # Run the shell script for the primary configuration + install(CODE "execute_process(COMMAND zsh ${PROJECT_BINARY_DIR}/AppleFrameworkBuild.zsh )") +endfunction() # pxr_create_apple_framework diff --git a/cmake/resources/AppleFrameworkBuild.zsh.in b/cmake/resources/AppleFrameworkBuild.zsh.in new file mode 100644 index 0000000000..4de1bdd927 --- /dev/null +++ b/cmake/resources/AppleFrameworkBuild.zsh.in @@ -0,0 +1,107 @@ +#!/usr/bin/env zsh + +# Creates an Apple framework for the given platform type +# documentation: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle +echo "⌛️ Creating @PXR_APPLE_FRAMEWORK_NAME@ ..." + +# Variables are substituted by CMake +CMAKE_INSTALL_PREFIX="@CMAKE_INSTALL_PREFIX@" +PROJECT_BINARY_DIR="@PROJECT_BINARY_DIR@" + +FRAMEWORK_NAME="@PXR_APPLE_FRAMEWORK_NAME@" +FRAMEWORK_DIR="${CMAKE_INSTALL_PREFIX}/${FRAMEWORK_NAME}.framework" +FRAMEWORK_HEADERS_DIR="${FRAMEWORK_DIR}/Headers" +FRAMEWORK_LIBRARIES_DIR="${FRAMEWORK_DIR}/Libraries" +FRAMEWORK_PLUGIN_DIR="${FRAMEWORK_LIBRARIES_DIR}/usd" +FRAMEWORK_ROOT_LIBRARY_NAME="@FRAMEWORK_ROOT_LIBRARY_NAME@" +EMBEDDED_BUILD=@EMBEDDED_BUILD@ +FRAMEWORK_RESOURCES_DIR="${FRAMEWORK_DIR}" +MATERIALX_SOURCE_LIBRARIES="${CMAKE_INSTALL_PREFIX}/libraries/" +BUNDLE_IDENTIFIER="@PXR_APPLE_IDENTIFIER_DOMAIN@.@PXR_APPLE_FRAMEWORK_NAME@" +CODESIGN_ID="@PXR_APPLE_CODESIGN_IDENTITY@" +OLD_RC_PATH="${CMAKE_INSTALL_PREFIX}/lib" + +function fix_linkage() { + readonly file=${1:?"A file path must be specified."} + readonly prepend="${FRAMEWORK_NAME}.framework/Libraries" + filename=$(basename ${file}) + # First, change the install name. This corresponds to LC_ID_DYLIB. + install_name_tool -id "@rpath/${prepend}/${filename}" ${file} + + parts=("${(@f)$(otool -l ${file})}") + for line in ${parts}; do + dylib_name="" + [[ $line =~ ' *name @rpath/(.*\.dylib)' ]] && dylib_name=$match[1] + if [ -n "${dylib_name}" ]; then + install_name_tool -change "@rpath/${dylib_name}" "@rpath/${prepend}/${dylib_name}" "${file}" + fi + if [[ $line == *"${OLD_RC_PATH}"* ]]; then + install_name_tool -delete_rpath ${OLD_RC_PATH} ${file} + fi + done + + codesign -f -s ${CODESIGN_ID} ${file} +} + +if [ "$EMBEDDED_BUILD" = false ];then + PLIST_ROOT="${FRAMEWORK_DIR}/Versions/A/Resources/" +fi + +# Remove the existing directory if it exists +if [ -d ${FRAMEWORK_DIR} ]; then + echo "Removing existing framework"; + rm -Rf ${FRAMEWORK_DIR}; +fi + +# Create the parent directory +echo "Creating directories..." +mkdir -p ${FRAMEWORK_DIR} +mkdir -p ${PLIST_ROOT} + +# Copy the plist over +echo "Copying files into ${FRAMEWORK_DIR}" +ditto "${PROJECT_BINARY_DIR}/Info.plist" "${PLIST_ROOT}/Info.plist" + +# Copy the primary directories over +ditto "${CMAKE_INSTALL_PREFIX}/include/" ${FRAMEWORK_HEADERS_DIR} +ditto "${CMAKE_INSTALL_PREFIX}/lib/" ${FRAMEWORK_LIBRARIES_DIR} +ditto "${CMAKE_INSTALL_PREFIX}/plugin/usd/" ${FRAMEWORK_PLUGIN_DIR} + + +# Remove any so files because boost generates them for iPhone +rm -rf ${FRAMEWORK_LIBRARIES_DIR}/cmake +for file in ${FRAMEWORK_LIBRARIES_DIR}/**/libboost*.so*; do + rm -f ${file} +done + +# Remove any static archive files as well +for file in ${FRAMEWORK_LIBRARIES_DIR}/**/*.a; do + rm -f ${file} +done + + +# Copy the MaterialX libraries if they exist +if [ -d "${MATERIALX_SOURCE_LIBRARIES}" ]; then + ditto ${MATERIALX_SOURCE_LIBRARIES} "${FRAMEWORK_LIBRARIES_DIR}/materialx/" +fi + + +echo "Correcting linkage on libraries..." +# The root file needs to be a binary that matches the framework name +mv "${FRAMEWORK_LIBRARIES_DIR}/${FRAMEWORK_ROOT_LIBRARY_NAME}" "${FRAMEWORK_DIR}/${FRAMEWORK_NAME}" +(cd ${FRAMEWORK_LIBRARIES_DIR} && ln -s "../${FRAMEWORK_NAME}" ${FRAMEWORK_ROOT_LIBRARY_NAME}) +fix_linkage "${FRAMEWORK_DIR}/${FRAMEWORK_NAME}" +install_name_tool -id "@rpath/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${FRAMEWORK_DIR}/${FRAMEWORK_NAME}" +install_name_tool -change "@rpath/${FRAMEWORK_NAME}.framework/Libraries/${FRAMEWORK_NAME}" "@rpath/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${FRAMEWORK_DIR}/${FRAMEWORK_NAME}" + +# Do Dylib fixing here +# This finds all dylibs, but (.) skips linked files +for file in ${FRAMEWORK_LIBRARIES_DIR}/**/*.dylib(.); do + fix_linkage ${file} +done + +# Sign the final framework +echo "Codesigning the framework..." +codesign --force --sign ${CODESIGN_ID} ${FRAMEWORK_DIR} --generate-entitlement-der --identifier ${BUNDLE_IDENTIFIER} + +echo "✅ Finished creating framework at ${FRAMEWORK_DIR}" \ No newline at end of file diff --git a/cmake/resources/Info.plist.in b/cmake/resources/Info.plist.in new file mode 100644 index 0000000000..548995b60e --- /dev/null +++ b/cmake/resources/Info.plist.in @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + @PXR_APPLE_FRAMEWORK_NAME@ + CFBundleIdentifier + @PXR_APPLE_IDENTIFIER_DOMAIN@.@PXR_APPLE_FRAMEWORK_NAME@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + @PXR_APPLE_FRAMEWORK_NAME@ + CFBundlePackageType + FMWK + CFBundleShortVersionString + @PXR_MAJOR_VERSION@.@PXR_MINOR_VERSION@.@PXR_PATCH_VERSION@ + CFBundleVersion + @PXR_MAJOR_VERSION@.@PXR_MINOR_VERSION@.@PXR_PATCH_VERSION@ + CSResourcesFileMapped + + + \ No newline at end of file diff --git a/pxr/base/plug/initConfig.cpp b/pxr/base/plug/initConfig.cpp index 11b1f11194..12a2233212 100644 --- a/pxr/base/plug/initConfig.cpp +++ b/pxr/base/plug/initConfig.cpp @@ -110,6 +110,10 @@ ARCH_CONSTRUCTOR(Plug_InitConfig, 2, void) _AppendPathList(&result, buildLocation, binaryPath); _AppendPathList(&result, pluginBuildLocation, binaryPath); +#ifdef PXR_BUILD_APPLE_FRAMEWORK + _AppendPathList(&result, "Libraries/usd", binaryPath); +#endif + #ifdef PXR_INSTALL_LOCATION _AppendPathList(&result, installLocation, binaryPath); #endif // PXR_INSTALL_LOCATION diff --git a/pxr/usd/usdMtlx/utils.cpp b/pxr/usd/usdMtlx/utils.cpp index 44ddcd2284..1768d4ec2d 100644 --- a/pxr/usd/usdMtlx/utils.cpp +++ b/pxr/usd/usdMtlx/utils.cpp @@ -33,6 +33,8 @@ #include "pxr/usd/sdf/types.h" #include "pxr/usd/sdr/shaderProperty.h" #include "pxr/base/arch/fileSystem.h" +#include "pxr/base/arch/symbols.h" +#include "pxr/base/arch/systemInfo.h" #include "pxr/base/gf/matrix3d.h" #include "pxr/base/gf/matrix4d.h" #include "pxr/base/gf/vec2f.h" @@ -199,6 +201,21 @@ _ComputeStdlibSearchPaths() stdlibSearchPaths = _MergeSearchPaths(stdlibSearchPaths, { PXR_MATERIALX_STDLIB_DIR }); #endif + +#ifdef PXR_BUILD_APPLE_FRAMEWORK + std::string binaryPath; + if (!ArchGetAddressInfo( + reinterpret_cast(&_ComputeStdlibSearchPaths), &binaryPath, + nullptr, nullptr, nullptr) + && binaryPath.empty()) + { + binaryPath = ArchGetExecutablePath(); + binaryPath = TfGetPathName(binaryPath); + } + stdlibSearchPaths = + _MergeSearchPaths(stdlibSearchPaths, { TfStringCatPaths(binaryPath, "../Libraries/materialx") }); +#endif + return stdlibSearchPaths; }