Skip to content

Commit

Permalink
Add support for Apple framework builds (AcademySoftwareFoundation#2020)
Browse files Browse the repository at this point in the history
This PR makes a few changes that have been derived from a corresponding PR to USD PixarAnimationStudios/OpenUSD#2969

The changes are:
1. Deprecates `MATERIALX_BUILD_IOS` in favor of using `CMAKE_SYSTEM_NAME`. This allows for better support for iPhone derived targets like visionOS. Earlier this would force it to iOS which can cause subtle issues when compiled against other SDKs.  See  https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-ios-tvos-visionos-or-watchos

2. Changes `TARGET_OS_IOS` define to `TARGET_OS_IPHONE` which is more correct to support iPhone derivatives. See     https://chaosinmotion.com/2021/08/02/things-to-remember-compiler-conditionals-for-macos-ios-etc/ . But the gist is `TARGET_OS_IOS` refers specifically to the modern iOS/iPadOS SDK, whereas `TARGET_OS_IPHONE` refers to all Apple SDKs that derived from the original iPhone. 

3. Add support for building as a Framework. This creates a special directory structure that streamlines embedding of MaterialX within Apps on Apple platforms to just dragging the project into the app in Xcode. No other linker and compiler configuration is necessary. See Framework notes below.
  • Loading branch information
dgovil authored Sep 25, 2024
1 parent 80e1b56 commit e6b9650
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
compiler: xcode
compiler_version: "15.4"
python: None
cmake_config: -DMATERIALX_BUILD_IOS=ON -DCMAKE_OSX_SYSROOT=`xcrun --sdk iphoneos --show-sdk-path` -DCMAKE_OSX_ARCHITECTURES=arm64
cmake_config: -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=`xcrun --sdk iphoneos --show-sdk-path` -DCMAKE_OSX_ARCHITECTURES=arm64

- name: Windows_VS2019_Win32_Python37
os: windows-2019
Expand Down
52 changes: 50 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,27 @@ option(MATERIALX_COVERAGE_ANALYSIS "Build MaterialX libraries with coverage anal
option(MATERIALX_DYNAMIC_ANALYSIS "Build MaterialX libraries with dynamic analysis on supporting platforms." OFF)
option(MATERIALX_OSL_LEGACY_CLOSURES "Build OSL shader generation supporting the legacy OSL closures." OFF)

option(MATERIALX_BUILD_IOS "Build MaterialX for iOS." OFF)
option(MATERIALX_BUILD_IOS "Build MaterialX for iOS. (Deprecated. Set CMAKE_SYSTEM_NAME instead)" OFF)
set(MATERIALX_BUILD_APPLE_EMBEDDED OFF)
if (MATERIALX_BUILD_IOS)
MESSAGE(WARNING "The MATERIALX_BUILD_IOS is deprecated. Set the CMAKE_SYSTEM_NAME to the platform instead")
set(CMAKE_SYSTEM_NAME iOS)
add_definitions(-DTARGET_OS_IOS=1)
endif()

# Cross Compilation detection as defined in CMake docs
# https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-ios-tvos-visionos-or-watchos
# Note: All these SDKs may not be supported by MaterialX
set(__build_apple_framework OFF)
if (CMAKE_SYSTEM_NAME MATCHES "iOS"
OR CMAKE_SYSTEM_NAME MATCHES "tvOS"
OR CMAKE_SYSTEM_NAME MATCHES "visionOS"
OR CMAKE_SYSTEM_NAME MATCHES "watchOS")
set(MATERIALX_BUILD_APPLE_EMBEDDED ON)
set(__build_apple_framework ${MATERIALX_BUILD_SHARED_LIBS})
# TARGET_OS_IPHONE refers to all IPHONE derived platforms
# https://chaosinmotion.com/2021/08/02/things-to-remember-compiler-conditionals-for-macos-ios-etc/
# This should be auto-defined, but leaving it in here because it was historically defined
add_definitions(-DTARGET_OS_IPHONE=1)
set(MATERIALX_BUILD_MONOLITHIC ON)
set(MATERIALX_BUILD_PYTHON OFF)
set(MATERIALX_BUILD_VIEWER OFF)
Expand All @@ -73,6 +90,18 @@ if (MATERIALX_BUILD_IOS)
set(MATERIALX_BUILD_TESTS OFF)
endif()

set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-" CACHE STRING "The Codesigning identity needed to sign compiled objects")
option(MATERIALX_BUILD_APPLE_FRAMEWORK "Build MaterialX as an Apple Framework" ${__build_apple_framework})
if (MATERIALX_BUILD_APPLE_FRAMEWORK)
add_definitions(-DBUILD_APPLE_FRAMEWORK)
set(MATERIALX_BUILD_MONOLITHIC ON)
set(MATERIALX_BUILD_PYTHON OFF)
set(MATERIALX_BUILD_VIEWER OFF)
set(MATERIALX_BUILD_GRAPH_EDITOR OFF)
set(MATERIALX_BUILD_TESTS OFF)
set(MATERIALX_BUILD_SHARED_LIBS ON)
endif()

if (MATERIALX_BUILD_JS)
set(MATERIALX_BUILD_RENDER OFF)
set(MATERIALX_BUILD_TESTS OFF)
Expand Down Expand Up @@ -163,6 +192,7 @@ mark_as_advanced(MATERIALX_INSTALL_STDLIB_PATH)
mark_as_advanced(MATERIALX_BUILD_JS)
mark_as_advanced(MATERIALX_EMSDK_PATH)
mark_as_advanced(MATERIALX_BUILD_IOS)
mark_as_advanced(MATERIALX_BUILD_APPLE_FRAMEWORK)
if (MATERIALX_BUILD_GEN_MDL)
mark_as_advanced(MATERIALX_MDLC_EXECUTABLE)
mark_as_advanced(MATERIALX_MDL_RENDER_EXECUTABLE)
Expand Down Expand Up @@ -496,6 +526,24 @@ if (MATERIALX_BUILD_MONOLITHIC)
# Note : we don't install the headers etc. here, and rely on each separate modules CMakeLists.txt
# to do that installation, thus we respect the build options configuration, and only install
# the headers for the modules we've built in to the monolithic build.

# Finally do the framework build if requested
# This uses a zsh script since zsh is guaranteed to exist on systems
if(MATERIALX_BUILD_APPLE_FRAMEWORK)
# Conform cmake formats to zsh expected formats
set(__embedded_build "false")
if (MATERIALX_BUILD_APPLE_EMBEDDED)
set(__embedded_build "true")
endif()

# Install the Info.plist and shell script
math(EXPR CFBUNDLEVERSION "${MATERIALX_MAJOR_VERSION} * 10000 + ${MATERIALX_MINOR_VERSION} * 100 + ${MATERIALX_BUILD_VERSION}")
configure_file(cmake/modules/Info.plist.in "${PROJECT_BINARY_DIR}/Info.plist" @ONLY)
configure_file(cmake/modules/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 )")
endif()
endif()
endif()

Expand Down
113 changes: 113 additions & 0 deletions cmake/modules/AppleFrameworkBuild.zsh.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/bin/zsh

# Creates an Apple framework for the given platform type
# documentation: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle
# https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html
echo "⌛️ Creating MaterialX.framework ..."

# Variables are substituted by CMake
CMAKE_INSTALL_PREFIX="@CMAKE_INSTALL_PREFIX@"
PROJECT_BINARY_DIR="@PROJECT_BINARY_DIR@"

FRAMEWORK_NAME="MaterialX"
FRAMEWORK_DIR="${CMAKE_INSTALL_PREFIX}/frameworks/${FRAMEWORK_NAME}.framework"
FRAMEWORK_LIBRARIES_DIR="${FRAMEWORK_DIR}/Libraries"
FRAMEWORK_ROOT_LIBRARY_NAME="libMaterialX.@[email protected]"
EMBEDDED_BUILD=@__embedded_build@
FRAMEWORK_RESOURCES_DIR="${FRAMEWORK_DIR}"
MATERIALX_SOURCE_LIBRARIES="${CMAKE_INSTALL_PREFIX}/libraries/"
BUNDLE_IDENTIFIER="org.aswf.materialx"
CODESIGN_ID="@CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_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}
}

# 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 Framework Directory: ${FRAMEWORK_DIR}"
mkdir -p ${FRAMEWORK_DIR}

if [ "$EMBEDDED_BUILD" = true ];then
FRAMEWORK_RESOURCES_DIR="${FRAMEWORK_DIR}/Resources"
FRAMEWORK_PLIST_LOCATION="${FRAMEWORK_DIR}/Info.plist"
FRAMEWORK_HEADERS_DIR="${FRAMEWORK_DIR}/Headers"
FRAMEWORK_LIB_PATH=""${FRAMEWORK_DIR}/${FRAMEWORK_NAME}""
else
FRAMEWORK_RESOURCES_DIR="${FRAMEWORK_DIR}/Versions/A/Resources/"
FRAMEWORK_PLIST_LOCATION="${FRAMEWORK_DIR}/Versions/A/Resources/Info.plist"
FRAMEWORK_HEADERS_DIR="${FRAMEWORK_DIR}/Versions/A/Headers"
FRAMEWORK_LIB_PATH="${FRAMEWORK_DIR}/Versions/A/${FRAMEWORK_NAME}"
fi

echo "Creating Resources Root: ${FRAMEWORK_RESOURCES_DIR}"
mkdir -p ${FRAMEWORK_RESOURCES_DIR}

echo "Creating Headers Root: ${FRAMEWORK_HEADERS_DIR}"
mkdir -p ${FRAMEWORK_HEADERS_DIR}

# Copy the plist over
echo "Copying files into ${FRAMEWORK_DIR}"
ditto "${PROJECT_BINARY_DIR}/Info.plist" "${FRAMEWORK_PLIST_LOCATION}"

# Copy the primary directories over
ditto "${CMAKE_INSTALL_PREFIX}/include/" ${FRAMEWORK_HEADERS_DIR}
ditto "${CMAKE_INSTALL_PREFIX}/libraries/" "${FRAMEWORK_RESOURCES_DIR}/libraries"
ditto "${CMAKE_INSTALL_PREFIX}/resources" "${FRAMEWORK_RESOURCES_DIR}/render"

cp "${CMAKE_INSTALL_PREFIX}/lib/${FRAMEWORK_ROOT_LIBRARY_NAME}" "${FRAMEWORK_LIB_PATH}"

# Setup symlinks
if [ "$EMBEDDED_BUILD" = false ];then
(cd "${FRAMEWORK_DIR}/Versions" && ln -s "A" "Current")
(cd ${FRAMEWORK_DIR} && ln -s "Versions/Current/Resources" "Resources")
(cd ${FRAMEWORK_DIR} && ln -s "Versions/Current/Headers" "Headers")
(cd ${FRAMEWORK_DIR} && ln -s "Versions/Current/${FRAMEWORK_NAME}" ${FRAMEWORK_NAME})
fi

# Fix the linkage on the primary dylib
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}"

# Frameworks require all includes to use the framework name as the prefix for automatic discovery
echo "Modifying headers..."
HEADER_SET=""
for i in $(cd $FRAMEWORK_HEADERS_DIR && ls -d */ | cut -f1 -d'/');do
HEADER_SET+="${i}|"
done;
# Sed on macOS is POSIX compliant and so uses different args than GNU
# Things to be aware of are [[:<:]] and [[:>:]] are for word boundaries and spaces are explicit literals
INCLUDE_PATTERN="^# *include [\"|<]([[:<:]](${HEADER_SET::-1})[[:>:]].*)[\"|>].*$"
find ${FRAMEWORK_HEADERS_DIR} -type f -name "*.h*" -print0 | xargs -0 sed -i "" -E "s,${INCLUDE_PATTERN},#include <${FRAMEWORK_NAME}/\1>,g"

# Sign the final framework
echo "Codesigning the framework..."
set -e
codesign --force --sign ${CODESIGN_ID} --generate-entitlement-der --identifier ${BUNDLE_IDENTIFIER} --verbose ${FRAMEWORK_DIR}

echo "✅ Finished creating framework at ${FRAMEWORK_DIR}"
24 changes: 24 additions & 0 deletions cmake/modules/Info.plist.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>MaterialX</string>
<key>CFBundleIdentifier</key>
<string>org.aswf.materialx</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>MaterialX</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>@MATERIALX_LIBRARY_VERSION@</string>
<key>CFBundleVersion</key>
<string>@CFBUNDLEVERSION@</string>
<key>CSResourcesFileMapped</key>
<true />
</dict>
</plist>
26 changes: 24 additions & 2 deletions source/MaterialXFormat/Util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#include <iostream>
#include <sstream>

#if defined(__APPLE__) && defined(BUILD_APPLE_FRAMEWORK)
#include <dlfcn.h>
#endif

MATERIALX_NAMESPACE_BEGIN

string readFile(const FilePath& filePath)
Expand Down Expand Up @@ -226,15 +230,33 @@ FileSearchPath getDefaultDataSearchPath()
{
const FilePath REQUIRED_LIBRARY_FOLDER("libraries/targets");
FilePath currentPath = FilePath::getModulePath();

FileSearchPath searchPath;
#if defined(BUILD_APPLE_FRAMEWORK)
const FilePath FRAMEWORK_RESOURCES("Resources");

Dl_info info;
if (dladdr(reinterpret_cast<void*>(&getDefaultDataSearchPath), &info))
{
FilePath path = FilePath(info.dli_fname);
if (!path.isEmpty())
{
path = path.getParentPath();
searchPath.append(path / FRAMEWORK_RESOURCES);
}
}
#endif

while (!currentPath.isEmpty())
{
if ((currentPath / REQUIRED_LIBRARY_FOLDER).exists())
{
return FileSearchPath(currentPath);
searchPath.append(FileSearchPath(currentPath));
break;
}
currentPath = currentPath.getParentPath();
}
return FileSearchPath();
return searchPath;
}

MATERIALX_NAMESPACE_END
4 changes: 2 additions & 2 deletions source/MaterialXRenderHw/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ file(GLOB materialx_source "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
file(GLOB materialx_headers "${CMAKE_CURRENT_SOURCE_DIR}/*.h*")

if(APPLE)
if (NOT MATERIALX_BUILD_IOS)
if (NOT MATERIALX_BUILD_APPLE_EMBEDDED)
find_library(COCOA_FRAMEWORK Cocoa)
endif()
elseif(UNIX)
Expand Down Expand Up @@ -31,7 +31,7 @@ if(APPLE)
PUBLIC
"-framework Foundation"
"-framework Metal")
if (NOT MATERIALX_BUILD_IOS)
if (NOT MATERIALX_BUILD_APPLE_EMBEDDED)
target_link_libraries(${TARGET_NAME}
PUBLIC
"-framework Cocoa"
Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXRenderHw/SimpleWindowIOS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#if defined(__APPLE__)

#ifdef TARGET_OS_IOS
#ifdef TARGET_OS_IPHONE

#include <MaterialXRenderHw/SimpleWindow.h>

Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXRenderHw/SimpleWindowMac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#if defined(__APPLE__)

#ifndef TARGET_OS_IOS
#ifndef TARGET_OS_IPHONE

#include <MaterialXRenderHw/SimpleWindow.h>
#include <MaterialXRenderHw/WindowCocoaWrappers.h>
Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXRenderHw/WindowCocoaWrappers.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#if defined (__APPLE__)

#ifndef TARGET_OS_IOS
#ifndef TARGET_OS_IPHONE

#import <Cocoa/Cocoa.h>
#import <AppKit/NSApplication.h>
Expand Down
2 changes: 1 addition & 1 deletion source/MaterialXRenderHw/WindowWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ WindowWrapper::WindowWrapper(ExternalWindowHandle externalHandle,
DisplayHandle display)
{
_externalHandle = externalHandle;
#ifndef TARGET_OS_IOS
#ifndef TARGET_OS_IPHONE
// Cache a pointer to the window.
_internalHandle = NSUtilGetView(externalHandle);
#else
Expand Down
4 changes: 2 additions & 2 deletions source/MaterialXRenderMsl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ if(POLICY CMP0072)
endif()

if(APPLE)
if(NOT MATERIALX_BUILD_IOS)
if(NOT MATERIALX_BUILD_APPLE_EMBEDDED)
find_library(COCOA_FRAMEWORK Cocoa)
find_package(OpenGL REQUIRED)
endif()
Expand Down Expand Up @@ -50,7 +50,7 @@ if(MSVC)
PUBLIC
Opengl32)
elseif(APPLE)
if(NOT MATERIALX_BUILD_IOS)
if(NOT MATERIALX_BUILD_APPLE_EMBEDDED)
target_link_libraries(${TARGET_NAME}
PUBLIC
"-framework Cocoa"
Expand Down

0 comments on commit e6b9650

Please sign in to comment.