Skip to content

Commit

Permalink
Add iOS build support for USD Core
Browse files Browse the repository at this point in the history
This PR adds **Core** iOS support to the OpenUSD project. This does not include Imaging, and any Imaging related components at this time. Imaging will be added in a follow up PR. MaterialX is also disabled as requested and the upgrade will be handled in a follow up PR.

It is a minimal version of PixarAnimationStudios#2455 against the latest `dev` branch.
Changes include:
* Using latest dev branch
* No imaging support. Will be added in a follow up PR.
* Makes use of CMake's inbuilt iOS support, negating the need for toolchain support
* Structures the code in such a way that we can add support for other iOS derived platforms+simulator in future PRs
* Swaps `ARCH_OS_IOS` with `ARCH_OS_IPHONE` to align with compiler directives. IPHONE refers to all derivative platforms, whereas iOS refers to only iPhone/iPad (Confusing but the case for historical reasons as [documented here](https://chaosinmotion.com/2021/08/02/things-to-remember-compiler-conditionals-for-macos-ios-etc/))
* TBB requires SDKROOT to be passed in or it can often go off and find some random compiler toolchain and fail.
* Add APPLE_EMBEDDED boolean to designate when using the CMake supported cross compilation targets. Added in Options.cmake so it can be used to configure defaults properly.
  • Loading branch information
dgovil committed Apr 22, 2024
1 parent 328e504 commit d825376
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 39 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ then build and install USD into `/path/to/my_usd_install_dir`.
> python OpenUSD/build_scripts/build_usd.py /path/to/my_usd_install_dir
```

##### MacOS:
##### macOS:

In a terminal, run `xcode-select` to ensure command line developer tools are
installed. Then run the script.
Expand All @@ -141,6 +141,16 @@ then build and install USD into `/path/to/my_usd_install_dir`.
> python OpenUSD/build_scripts/build_usd.py /path/to/my_usd_install_dir
```

###### iOS

When building from a macOS system, you can cross compile for iOS based platforms.

iOS builds currently do not support Imaging.
Additionally, they will not support Python bindings or command line tools.

To build for iOS, add the `--build-target iOS` parameter.


##### Windows:

Launch the "x64 Native Tools Command Prompt" for your version of Visual Studio
Expand Down
118 changes: 92 additions & 26 deletions build_scripts/apple_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,44 +35,47 @@
import platform
import shlex
import subprocess
import re
from typing import Optional, List

TARGET_NATIVE = "native"
TARGET_X86 = "x86_64"
TARGET_ARM64 = "arm64"
TARGET_UNIVERSAL = "universal"
TARGET_IOS = "ios"

EMBEDDED_PLATFORMS = [TARGET_IOS]

def GetBuildTargets():
return [TARGET_NATIVE,
TARGET_X86,
TARGET_ARM64,
TARGET_UNIVERSAL]
TARGET_UNIVERSAL,
TARGET_IOS]

def GetBuildTargetDefault():
return TARGET_NATIVE;
return TARGET_NATIVE

def MacOS():
return platform.system() == "Darwin"

def GetLocale():
return sys.stdout.encoding or locale.getdefaultlocale()[1] or "UTF-8"

def GetCommandOutput(command):
def GetCommandOutput(command, **kwargs):
"""Executes the specified command and returns output or None."""
try:
return subprocess.check_output(
shlex.split(command),
stderr=subprocess.STDOUT).decode(GetLocale(), 'replace').strip()
except subprocess.CalledProcessError:
pass
return None
return subprocess.check_output(command, stderr=subprocess.STDOUT, **kwargs).decode(GetLocale(), 'replace').strip()
except:
return None

def GetTargetArmArch():
# Allows the arm architecture string to be overridden by
# setting MACOS_ARM_ARCHITECTURE
return os.environ.get('MACOS_ARM_ARCHITECTURE') or TARGET_ARM64

def GetHostArch():
macArch = GetCommandOutput('arch').strip()
macArch = GetCommandOutput(["arch"])
if macArch == "i386" or macArch == TARGET_X86:
macArch = TARGET_X86
else:
Expand All @@ -85,7 +88,7 @@ def GetTargetArch(context):
else:
if context.targetX86:
macTargets = TARGET_X86
if context.targetARM64:
if context.targetARM64 or context.buildTarget in EMBEDDED_PLATFORMS:
macTargets = GetTargetArmArch()
if context.targetUniversal:
macTargets = TARGET_X86 + ";" + GetTargetArmArch()
Expand All @@ -106,6 +109,8 @@ def GetTargetArchPair(context):
primaryArch = TARGET_X86
if context.targetARM64:
primaryArch = GetTargetArmArch()
if context.targetIos:
primaryArch = TARGET_IOS
if context.targetUniversal:
primaryArch = GetHostArch()
if (primaryArch == TARGET_X86):
Expand All @@ -118,18 +123,33 @@ def GetTargetArchPair(context):
def SupportsMacOSUniversalBinaries():
if not MacOS():
return False
XcodeOutput = GetCommandOutput('/usr/bin/xcodebuild -version')
XcodeOutput = GetCommandOutput(["/usr/bin/xcodebuild", "-version"])
XcodeFind = XcodeOutput.rfind('Xcode ', 0, len(XcodeOutput))
XcodeVersion = XcodeOutput[XcodeFind:].split(' ')[1]
return (XcodeVersion > '11.0')


def GetSDKRoot(context) -> Optional[str]:
sdk = "macosx"
if context.buildTarget == TARGET_IOS:
sdk = "iphoneos"

for arg in (context.cmakeBuildArgs or '').split():
if "CMAKE_OSX_SYSROOT" in arg:
override = arg.split('=')[1].strip('"').strip()
if override:
sdk = override
return GetCommandOutput(["xcrun", "--sdk", sdk, "--show-sdk-path"])


def SetTarget(context, targetName):
context.targetNative = (targetName == TARGET_NATIVE)
context.targetX86 = (targetName == TARGET_X86)
context.targetARM64 = (targetName == GetTargetArmArch())
context.targetUniversal = (targetName == TARGET_UNIVERSAL)
context.targetIos = (targetName == TARGET_IOS)
if context.targetUniversal and not SupportsMacOSUniversalBinaries():
self.targetUniversal = False
context.targetUniversal = False
raise ValueError(
"Universal binaries only supported in macOS 11.0 and later.")

Expand All @@ -150,26 +170,63 @@ def ExtractFilesRecursive(path, cond):
files.append(os.path.join(r, file))
return files

def CodesignFiles(files):
SDKVersion = subprocess.check_output(
['xcodebuild', '-version']).strip()[6:10]
codeSignIDs = subprocess.check_output(
['security', 'find-identity', '-vp', 'codesigning'])
def _GetCodeSignStringFromTerminal():
codeSignIDs = GetCommandOutput(['security', 'find-identity', '-vp', 'codesigning'])
return codeSignIDs

codeSignID = "-"
def GetCodeSignID():
if os.environ.get('CODE_SIGN_ID'):
codeSignID = os.environ.get('CODE_SIGN_ID')
elif float(SDKVersion) >= 11.0 and \
codeSignIDs.find(b'Apple Development') != -1:
codeSignID = "Apple Development"
elif codeSignIDs.find(b'Mac Developer') != -1:
codeSignID = "Mac Developer"
return os.environ.get('CODE_SIGN_ID')

codeSignIDs = _GetCodeSignStringFromTerminal()
if not codeSignIDs:
return "-"
for codeSignID in codeSignIDs.splitlines():
if "CSSMERR_TP_CERT_REVOKED" in codeSignID:
continue
if ")" not in codeSignID:
continue
codeSignID = codeSignID.split()[1]
break
else:
raise RuntimeError("Could not find a valid codesigning ID")

return codeSignID or "-"

def GetCodeSignIDHash():
codeSignIDs = _GetCodeSignStringFromTerminal()
try:
return re.findall(r'\(.*?\)', codeSignIDs)[0][1:-1]
except:
raise Exception("Unable to parse codesign ID hash")

def GetDevelopmentTeamID():
if os.environ.get("DEVELOPMENT_TEAM"):
return os.environ.get("DEVELOPMENT_TEAM")
codesignID = GetCodeSignIDHash()

certs = subprocess.check_output(["security", "find-certificate", "-c", codesignID, "-p"])
subject = GetCommandOutput(["openssl", "x509", "-subject"], input=certs)
subject = subject.splitlines()[0]

# Extract the Organizational Unit (OU field) from the cert
try:
team = [elm for elm in subject.split(
'/') if elm.startswith('OU')][0].split('=')[1]
if team is not None and team != "":
return team
except Exception as ex:
raise Exception("No development team found with exception " + ex)

def CodesignFiles(files):
codeSignID = GetCodeSignID()

for f in files:
subprocess.call(['codesign', '-f', '-s', '{codesignid}'
.format(codesignid=codeSignID), f],
.format(codesignid=codeSignID), f],
stdout=devout, stderr=devout)


def Codesign(install_path, verbose_output=False):
if not MacOS():
return False
Expand Down Expand Up @@ -217,3 +274,12 @@ def CreateUniversalBinaries(context, libNames, x86Dir, armDir):
instDir=context.instDir, libName=targetName),
outputName)
return lipoCommands

def ConfigureCMakeExtraArgs(context, args:List[str]) -> List[str]:
system_name = None
if context.buildTarget == TARGET_IOS:
system_name = "iOS"

if system_name:
args.append(f"-DCMAKE_SYSTEM_NAME={system_name}")
return args
52 changes: 40 additions & 12 deletions build_scripts/build_usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def GetCPUCount():
except NotImplementedError:
return 1

def Run(cmd, logCommandOutput = True):
def Run(cmd, logCommandOutput = True, env = None):
"""Run the specified command in a subprocess."""
PrintInfo('Running "{cmd}"'.format(cmd=cmd))

Expand All @@ -266,7 +266,7 @@ def Run(cmd, logCommandOutput = True):
# code will handle them.
if logCommandOutput:
p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
stderr=subprocess.STDOUT, env=env)
while True:
l = p.stdout.readline().decode(GetLocale(), 'replace')
if l:
Expand Down Expand Up @@ -421,6 +421,7 @@ def RunCMake(context, force, extraArgs = None):
extraArgs.append('-DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO')

extraArgs.append('-DCMAKE_OSX_ARCHITECTURES={0}'.format(targetArch))
extraArgs = apple_utils.ConfigureCMakeExtraArgs(context, extraArgs)

if context.ignorePaths:
ignoredPaths = ";".join(context.ignorePaths)
Expand Down Expand Up @@ -690,6 +691,19 @@ def AnyPythonDependencies(deps):

def InstallZlib(context, force, buildArgs):
with CurrentWorkingDirectory(DownloadURL(ZLIB_URL, context, force)):
# The following test files aren't portable to embedded platforms.
# They're not required for use on any platforms, so we elide them for efficiency
PatchFile("CMakeLists.txt",
[("add_executable(example test/example.c)",
""),
("add_executable(minigzip test/minigzip.c)",
""),
("target_link_libraries(example zlib)",
""),
("target_link_libraries(minigzip zlib)",
""),
("add_test(example example)",
"")])
RunCMake(context, force, buildArgs)

ZLIB = Dependency("zlib", InstallZlib, "include/zlib.h")
Expand Down Expand Up @@ -724,8 +738,8 @@ def InstallBoost_Helper(context, force, buildArgs):
# However, there are some cases where a newer version is required.
# - Building with Python 3.11 requires boost 1.82.0 or newer
# (https://github.com/boostorg/python/commit/a218ba)
# - Building on MacOS requires v1.82.0 or later for C++17 support starting
# with Xcode 15. We choose to use this version for all MacOS builds for
# - Building on MacOS requires v1.82.0 or later for C++17 support starting
# with Xcode 15. We choose to use this version for all MacOS builds for
# simplicity."
# - Building with Python 3.10 requires boost 1.76.0 or newer
# (https://github.com/boostorg/python/commit/cbd2d9)
Expand Down Expand Up @@ -822,6 +836,10 @@ def InstallBoost_Helper(context, force, buildArgs):
'--with-regex'
]

if MacOS() and context.buildTarget in apple_utils.EMBEDDED_PLATFORMS:
# Treat all embedded platforms as iphone derivatives since we're not actually building anything of note
b2_settings.append("target-os=iphone")

if context.buildPython:
b2_settings.append("--with-python")
pythonInfo = GetPythonInfo(context)
Expand Down Expand Up @@ -1004,10 +1022,16 @@ def InstallTBB_MacOS(context, force, buildArgs):
def _RunBuild(arch):
if not arch:
return
makeTBBCmd = 'make -j{procs} arch={arch} {buildArgs}'.format(
platformArg = ""
env = os.environ.copy()
env["SDKROOT"] = apple_utils.GetSDKRoot(context)
if context.build_target in apple_utils.EMBEDDED_PLATFORMS:
platformArg = f"target={context.build_target}"
makeTBBCmd = 'make -j{procs} arch={arch} {platformArg} {buildArgs}'.format(
arch=arch, procs=context.numJobs,
platformArg=platformArg,
buildArgs=" ".join(buildArgs))
Run(makeTBBCmd)
Run(makeTBBCmd, env=env)

_RunBuild(primaryArch)
_RunBuild(secondaryArch)
Expand Down Expand Up @@ -2610,19 +2634,23 @@ def FormatBuildArguments(buildArgs):
if context.macOSCodesign:
apple_utils.Codesign(context.usdInstDir, verbosity > 1)

Print("""
Success! To use USD, please ensure that you have:""")
printInstructions = any([context.buildPython, context.buildTools, context.buildPrman])
if printInstructions:
Print("\nSuccess! To use USD, please ensure that you have:")
else:
Print("\nSuccess! USD libraries were built.")

if context.buildPython:
Print("""
The following in your PYTHONPATH environment variable:
{requiredInPythonPath}""".format(
requiredInPythonPath="\n ".join(sorted(requiredInPythonPath))))

Print("""
The following in your PATH environment variable:
{requiredInPath}
""".format(requiredInPath="\n ".join(sorted(requiredInPath))))
if context.buildTools:
Print("""
The following in your PATH environment variable:
{requiredInPath}
""".format(requiredInPath="\n ".join(sorted(requiredInPath))))

if context.buildPrman:
Print("See documentation at http://openusd.org/docs/RenderMan-USD-Imaging-Plugin.html "
Expand Down

0 comments on commit d825376

Please sign in to comment.