diff --git a/README.md b/README.md index 77a2b28646..381c4822b8 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/build_scripts/apple_utils.py b/build_scripts/apple_utils.py index 2c82243896..05bc6cd16a 100644 --- a/build_scripts/apple_utils.py +++ b/build_scripts/apple_utils.py @@ -35,20 +35,26 @@ 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" @@ -56,15 +62,12 @@ def MacOS(): 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 @@ -72,7 +75,7 @@ def GetTargetArmArch(): 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: @@ -80,12 +83,14 @@ def GetHostArch(): return macArch def GetTargetArch(context): + if context.buildTarget in EMBEDDED_PLATFORMS: + return GetTargetArmArch() if context.targetNative: macTargets = GetHostArch() 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() @@ -106,6 +111,8 @@ def GetTargetArchPair(context): primaryArch = TARGET_X86 if context.targetARM64: primaryArch = GetTargetArmArch() + if context.buildTarget in EMBEDDED_PLATFORMS: + primaryArch = GetTargetArmArch() if context.targetUniversal: primaryArch = GetHostArch() if (primaryArch == TARGET_X86): @@ -118,18 +125,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.") @@ -138,7 +160,7 @@ def GetTargetName(context): TARGET_X86 if context.targetX86 else GetTargetArmArch() if context.targetARM64 else TARGET_UNIVERSAL if context.targetUniversal else - "") + context.buildTarget) devout = open(os.devnull, 'w') @@ -150,26 +172,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 @@ -217,3 +276,19 @@ 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}") + args.append(f"-DCMAKE_OSX_SYSROOT={GetSDKRoot(context)}") + + # CMake gets confused trying to find things when setting the system name + # See https://discourse.cmake.org/t/find-package-stops-working-when-cmake-system-name-ios/4609/8 + args.append(f"-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH") + args.append(f"-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH") + args.append(f"-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH") + return args \ No newline at end of file diff --git a/build_scripts/build_usd.py b/build_scripts/build_usd.py index 6a3aa3141f..4359c98e33 100644 --- a/build_scripts/build_usd.py +++ b/build_scripts/build_usd.py @@ -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)) @@ -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: @@ -275,7 +275,7 @@ def Run(cmd, logCommandOutput = True): elif p.poll() is not None: break else: - p = subprocess.Popen(shlex.split(cmd)) + p = subprocess.Popen(shlex.split(cmd), env=env) p.wait() if p.returncode != 0: @@ -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) @@ -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") @@ -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) @@ -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) @@ -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.buildTarget in apple_utils.EMBEDDED_PLATFORMS: + platformArg = f"target={context.buildTarget.lower()}" + 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) @@ -1483,6 +1507,13 @@ def InstallMaterialX(context, force, buildArgs): cmakeOptions = ['-DMATERIALX_BUILD_SHARED_LIBS=ON', '-DMATERIALX_BUILD_TESTS=OFF' ] + + if MacOS() and context.buildTarget in apple_utils.EMBEDDED_PLATFORMS: + cmakeOptions.extend([ + '-DMATERIALX_BUILD_GEN_MSL=ON', + '-DMATERIALX_BUILD_GEN_GLSL=OFF', + '-DMATERIALX_BUILD_IOS=ON']) + cmakeOptions += buildArgs RunCMake(context, force, cmakeOptions) @@ -2190,6 +2221,16 @@ def __init__(self, args): # - USD Imaging self.buildUsdImaging = (args.build_imaging == USD_IMAGING) + # Disable Features for Apple Embedded platforms + if MacOS() and self.buildTarget in apple_utils.EMBEDDED_PLATFORMS: + self.buildImaging = False + self.buildUsdImaging = False + self.buildTools = False + self.buildPython = False + self.enableOpenVDB = False + self.enablePtex = False + + # - usdview self.buildUsdview = (self.buildUsdImaging and self.buildPython and @@ -2610,8 +2651,11 @@ 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(""" @@ -2619,10 +2663,11 @@ def FormatBuildArguments(buildArgs): {requiredInPythonPath}""".format( requiredInPythonPath="\n ".join(sorted(requiredInPythonPath)))) -Print(""" +if context.buildTools: + Print(""" The following in your PATH environment variable: {requiredInPath} -""".format(requiredInPath="\n ".join(sorted(requiredInPath)))) + """.format(requiredInPath="\n ".join(sorted(requiredInPath)))) if context.buildPrman: Print("See documentation at http://openusd.org/docs/RenderMan-USD-Imaging-Plugin.html "