Skip to content

Commit 6f1bb3a

Browse files
committed
Add iOS build support for USD Core
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 #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.
1 parent 328e504 commit 6f1bb3a

3 files changed

Lines changed: 153 additions & 39 deletions

File tree

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ then build and install USD into `/path/to/my_usd_install_dir`.
129129
> python OpenUSD/build_scripts/build_usd.py /path/to/my_usd_install_dir
130130
```
131131

132-
##### MacOS:
132+
##### macOS:
133133

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

144+
###### iOS
145+
146+
When building from a macOS system, you can cross compile for iOS based platforms.
147+
148+
iOS builds currently do not support Imaging.
149+
Additionally, they will not support Python bindings or command line tools.
150+
151+
To build for iOS, add the `--build-target iOS` parameter.
152+
153+
144154
##### Windows:
145155

146156
Launch the "x64 Native Tools Command Prompt" for your version of Visual Studio

build_scripts/apple_utils.py

Lines changed: 92 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,44 +35,47 @@
3535
import platform
3636
import shlex
3737
import subprocess
38+
import re
39+
from typing import Optional, List
3840

3941
TARGET_NATIVE = "native"
4042
TARGET_X86 = "x86_64"
4143
TARGET_ARM64 = "arm64"
4244
TARGET_UNIVERSAL = "universal"
45+
TARGET_IOS = "ios"
46+
47+
EMBEDDED_PLATFORMS = [TARGET_IOS]
4348

4449
def GetBuildTargets():
4550
return [TARGET_NATIVE,
4651
TARGET_X86,
4752
TARGET_ARM64,
48-
TARGET_UNIVERSAL]
53+
TARGET_UNIVERSAL,
54+
TARGET_IOS]
4955

5056
def GetBuildTargetDefault():
51-
return TARGET_NATIVE;
57+
return TARGET_NATIVE
5258

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

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

59-
def GetCommandOutput(command):
65+
def GetCommandOutput(command, **kwargs):
6066
"""Executes the specified command and returns output or None."""
6167
try:
62-
return subprocess.check_output(
63-
shlex.split(command),
64-
stderr=subprocess.STDOUT).decode(GetLocale(), 'replace').strip()
65-
except subprocess.CalledProcessError:
66-
pass
67-
return None
68+
return subprocess.check_output(command, stderr=subprocess.STDOUT, **kwargs).decode(GetLocale(), 'replace').strip()
69+
except:
70+
return None
6871

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

7477
def GetHostArch():
75-
macArch = GetCommandOutput('arch').strip()
78+
macArch = GetCommandOutput(["arch"])
7679
if macArch == "i386" or macArch == TARGET_X86:
7780
macArch = TARGET_X86
7881
else:
@@ -85,7 +88,7 @@ def GetTargetArch(context):
8588
else:
8689
if context.targetX86:
8790
macTargets = TARGET_X86
88-
if context.targetARM64:
91+
if context.targetARM64 or context.buildTarget in EMBEDDED_PLATFORMS:
8992
macTargets = GetTargetArmArch()
9093
if context.targetUniversal:
9194
macTargets = TARGET_X86 + ";" + GetTargetArmArch()
@@ -106,6 +109,8 @@ def GetTargetArchPair(context):
106109
primaryArch = TARGET_X86
107110
if context.targetARM64:
108111
primaryArch = GetTargetArmArch()
112+
if context.targetIos:
113+
primaryArch = TARGET_IOS
109114
if context.targetUniversal:
110115
primaryArch = GetHostArch()
111116
if (primaryArch == TARGET_X86):
@@ -118,18 +123,33 @@ def GetTargetArchPair(context):
118123
def SupportsMacOSUniversalBinaries():
119124
if not MacOS():
120125
return False
121-
XcodeOutput = GetCommandOutput('/usr/bin/xcodebuild -version')
126+
XcodeOutput = GetCommandOutput(["/usr/bin/xcodebuild", "-version"])
122127
XcodeFind = XcodeOutput.rfind('Xcode ', 0, len(XcodeOutput))
123128
XcodeVersion = XcodeOutput[XcodeFind:].split(' ')[1]
124129
return (XcodeVersion > '11.0')
125130

131+
132+
def GetSDKRoot(context) -> Optional[str]:
133+
sdk = "macosx"
134+
if context.buildTarget == TARGET_IOS:
135+
sdk = "iphoneos"
136+
137+
for arg in (context.cmakeBuildArgs or '').split():
138+
if "CMAKE_OSX_SYSROOT" in arg:
139+
override = arg.split('=')[1].strip('"').strip()
140+
if override:
141+
sdk = override
142+
return GetCommandOutput(["xcrun", "--sdk", sdk, "--show-sdk-path"])
143+
144+
126145
def SetTarget(context, targetName):
127146
context.targetNative = (targetName == TARGET_NATIVE)
128147
context.targetX86 = (targetName == TARGET_X86)
129148
context.targetARM64 = (targetName == GetTargetArmArch())
130149
context.targetUniversal = (targetName == TARGET_UNIVERSAL)
150+
context.targetIos = (targetName == TARGET_IOS)
131151
if context.targetUniversal and not SupportsMacOSUniversalBinaries():
132-
self.targetUniversal = False
152+
context.targetUniversal = False
133153
raise ValueError(
134154
"Universal binaries only supported in macOS 11.0 and later.")
135155

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

153-
def CodesignFiles(files):
154-
SDKVersion = subprocess.check_output(
155-
['xcodebuild', '-version']).strip()[6:10]
156-
codeSignIDs = subprocess.check_output(
157-
['security', 'find-identity', '-vp', 'codesigning'])
173+
def _GetCodeSignStringFromTerminal():
174+
codeSignIDs = GetCommandOutput(['security', 'find-identity', '-vp', 'codesigning'])
175+
return codeSignIDs
158176

159-
codeSignID = "-"
177+
def GetCodeSignID():
160178
if os.environ.get('CODE_SIGN_ID'):
161-
codeSignID = os.environ.get('CODE_SIGN_ID')
162-
elif float(SDKVersion) >= 11.0 and \
163-
codeSignIDs.find(b'Apple Development') != -1:
164-
codeSignID = "Apple Development"
165-
elif codeSignIDs.find(b'Mac Developer') != -1:
166-
codeSignID = "Mac Developer"
179+
return os.environ.get('CODE_SIGN_ID')
180+
181+
codeSignIDs = _GetCodeSignStringFromTerminal()
182+
if not codeSignIDs:
183+
return "-"
184+
for codeSignID in codeSignIDs.splitlines():
185+
if "CSSMERR_TP_CERT_REVOKED" in codeSignID:
186+
continue
187+
if ")" not in codeSignID:
188+
continue
189+
codeSignID = codeSignID.split()[1]
190+
break
191+
else:
192+
raise RuntimeError("Could not find a valid codesigning ID")
193+
194+
return codeSignID or "-"
195+
196+
def GetCodeSignIDHash():
197+
codeSignIDs = _GetCodeSignStringFromTerminal()
198+
try:
199+
return re.findall(r'\(.*?\)', codeSignIDs)[0][1:-1]
200+
except:
201+
raise Exception("Unable to parse codesign ID hash")
202+
203+
def GetDevelopmentTeamID():
204+
if os.environ.get("DEVELOPMENT_TEAM"):
205+
return os.environ.get("DEVELOPMENT_TEAM")
206+
codesignID = GetCodeSignIDHash()
207+
208+
certs = subprocess.check_output(["security", "find-certificate", "-c", codesignID, "-p"])
209+
subject = GetCommandOutput(["openssl", "x509", "-subject"], input=certs)
210+
subject = subject.splitlines()[0]
211+
212+
# Extract the Organizational Unit (OU field) from the cert
213+
try:
214+
team = [elm for elm in subject.split(
215+
'/') if elm.startswith('OU')][0].split('=')[1]
216+
if team is not None and team != "":
217+
return team
218+
except Exception as ex:
219+
raise Exception("No development team found with exception " + ex)
220+
221+
def CodesignFiles(files):
222+
codeSignID = GetCodeSignID()
167223

168224
for f in files:
169225
subprocess.call(['codesign', '-f', '-s', '{codesignid}'
170-
.format(codesignid=codeSignID), f],
226+
.format(codesignid=codeSignID), f],
171227
stdout=devout, stderr=devout)
172228

229+
173230
def Codesign(install_path, verbose_output=False):
174231
if not MacOS():
175232
return False
@@ -217,3 +274,12 @@ def CreateUniversalBinaries(context, libNames, x86Dir, armDir):
217274
instDir=context.instDir, libName=targetName),
218275
outputName)
219276
return lipoCommands
277+
278+
def ConfigureCMakeExtraArgs(context, args:List[str]) -> List[str]:
279+
system_name = None
280+
if context.buildTarget == TARGET_IOS:
281+
system_name = "iOS"
282+
283+
if system_name:
284+
args.append(f"-DCMAKE_SYSTEM_NAME={system_name}")
285+
return args

build_scripts/build_usd.py

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def GetCPUCount():
252252
except NotImplementedError:
253253
return 1
254254

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

@@ -266,7 +266,7 @@ def Run(cmd, logCommandOutput = True):
266266
# code will handle them.
267267
if logCommandOutput:
268268
p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE,
269-
stderr=subprocess.STDOUT)
269+
stderr=subprocess.STDOUT, env=env)
270270
while True:
271271
l = p.stdout.readline().decode(GetLocale(), 'replace')
272272
if l:
@@ -421,6 +421,7 @@ def RunCMake(context, force, extraArgs = None):
421421
extraArgs.append('-DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO')
422422

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

425426
if context.ignorePaths:
426427
ignoredPaths = ";".join(context.ignorePaths)
@@ -690,6 +691,19 @@ def AnyPythonDependencies(deps):
690691

691692
def InstallZlib(context, force, buildArgs):
692693
with CurrentWorkingDirectory(DownloadURL(ZLIB_URL, context, force)):
694+
# The following test files aren't portable to embedded platforms.
695+
# They're not required for use on any platforms, so we elide them for efficiency
696+
PatchFile("CMakeLists.txt",
697+
[("add_executable(example test/example.c)",
698+
""),
699+
("add_executable(minigzip test/minigzip.c)",
700+
""),
701+
("target_link_libraries(example zlib)",
702+
""),
703+
("target_link_libraries(minigzip zlib)",
704+
""),
705+
("add_test(example example)",
706+
"")])
693707
RunCMake(context, force, buildArgs)
694708

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

839+
if MacOS() and context.buildTarget in apple_utils.EMBEDDED_PLATFORMS:
840+
# Treat all embedded platforms as iphone derivatives since we're not actually building anything of note
841+
b2_settings.append("target-os=iphone")
842+
825843
if context.buildPython:
826844
b2_settings.append("--with-python")
827845
pythonInfo = GetPythonInfo(context)
@@ -1004,10 +1022,16 @@ def InstallTBB_MacOS(context, force, buildArgs):
10041022
def _RunBuild(arch):
10051023
if not arch:
10061024
return
1007-
makeTBBCmd = 'make -j{procs} arch={arch} {buildArgs}'.format(
1025+
platformArg = ""
1026+
env = os.environ.copy()
1027+
env["SDKROOT"] = apple_utils.GetSDKRoot(context)
1028+
if context.buildTarget in apple_utils.EMBEDDED_PLATFORMS:
1029+
platformArg = f"target={context.buildTarget}"
1030+
makeTBBCmd = 'make -j{procs} arch={arch} {platformArg} {buildArgs}'.format(
10081031
arch=arch, procs=context.numJobs,
1032+
platformArg=platformArg,
10091033
buildArgs=" ".join(buildArgs))
1010-
Run(makeTBBCmd)
1034+
Run(makeTBBCmd, env=env)
10111035

10121036
_RunBuild(primaryArch)
10131037
_RunBuild(secondaryArch)
@@ -2190,6 +2214,16 @@ def __init__(self, args):
21902214
# - USD Imaging
21912215
self.buildUsdImaging = (args.build_imaging == USD_IMAGING)
21922216

2217+
# Disable Features for Apple Embedded platforms
2218+
if MacOS() and self.buildTarget in apple_utils.EMBEDDED_PLATFORMS:
2219+
self.buildImaging = False
2220+
self.buildUsdImaging = False
2221+
self.buildTools = False
2222+
self.buildPython = False
2223+
self.enableOpenVDB = False
2224+
self.enablePtex = False
2225+
2226+
21932227
# - usdview
21942228
self.buildUsdview = (self.buildUsdImaging and
21952229
self.buildPython and
@@ -2610,19 +2644,23 @@ def FormatBuildArguments(buildArgs):
26102644
if context.macOSCodesign:
26112645
apple_utils.Codesign(context.usdInstDir, verbosity > 1)
26122646

2613-
Print("""
2614-
Success! To use USD, please ensure that you have:""")
2647+
printInstructions = any([context.buildPython, context.buildTools, context.buildPrman])
2648+
if printInstructions:
2649+
Print("\nSuccess! To use USD, please ensure that you have:")
2650+
else:
2651+
Print("\nSuccess! USD libraries were built.")
26152652

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

2622-
Print("""
2623-
The following in your PATH environment variable:
2624-
{requiredInPath}
2625-
""".format(requiredInPath="\n ".join(sorted(requiredInPath))))
2659+
if context.buildTools:
2660+
Print("""
2661+
The following in your PATH environment variable:
2662+
{requiredInPath}
2663+
""".format(requiredInPath="\n ".join(sorted(requiredInPath))))
26262664

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

0 commit comments

Comments
 (0)