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;
}