|
4 | 4 | # Licensed under the terms set forth in the LICENSE.txt file available at |
5 | 5 | # https://openusd.org/license. |
6 | 6 | # |
7 | | - |
8 | 7 | # Utilities for managing Apple OS build concerns. |
9 | 8 | # |
10 | 9 | # NOTE: This file and its contents may change significantly as we continue |
|
15 | 14 | import sys |
16 | 15 | import locale |
17 | 16 | import os |
| 17 | +import re |
18 | 18 | import platform |
19 | 19 | import shlex |
20 | 20 | import subprocess |
21 | | -from typing import Optional, List |
| 21 | +import glob |
| 22 | +from typing import Optional, List, Dict |
22 | 23 |
|
23 | 24 | TARGET_NATIVE = "native" |
24 | 25 | TARGET_X86 = "x86_64" |
@@ -115,10 +116,8 @@ def GetTargetArchPair(context): |
115 | 116 | def SupportsMacOSUniversalBinaries(): |
116 | 117 | if not MacOS(): |
117 | 118 | return False |
118 | | - XcodeOutput = GetCommandOutput(["/usr/bin/xcodebuild", "-version"]) |
119 | | - XcodeFind = XcodeOutput.rfind('Xcode ', 0, len(XcodeOutput)) |
120 | | - XcodeVersion = XcodeOutput[XcodeFind:].split(' ')[1] |
121 | | - return (XcodeVersion > '11.0') |
| 119 | + XcodeVersion = GetXcodeVersion()[0] |
| 120 | + return (XcodeVersion > 11) |
122 | 121 |
|
123 | 122 | def GetSDKRoot(context) -> Optional[str]: |
124 | 123 | sdk = "macosx" |
@@ -163,36 +162,195 @@ def ExtractFilesRecursive(path, cond): |
163 | 162 | files.append(os.path.join(r, file)) |
164 | 163 | return files |
165 | 164 |
|
166 | | -def CodesignFiles(files): |
167 | | - SDKVersion = subprocess.check_output( |
168 | | - ['xcodebuild', '-version']).strip()[6:10] |
169 | | - codeSignIDs = subprocess.check_output( |
| 165 | +def _GetCodeSignStringFromTerminal(): |
| 166 | + """Return the output from the string codesigning variables""" |
| 167 | + codeSignIDs = GetCommandOutput( |
170 | 168 | ['security', 'find-identity', '-vp', 'codesigning']) |
| 169 | + return codeSignIDs |
| 170 | + |
| 171 | + |
| 172 | +def GetXcodeVersion(): |
| 173 | + output = GetCommandOutput(['xcodebuild', '-version']).split() |
| 174 | + version = float(output[1]) |
| 175 | + build = output[-1] |
| 176 | + |
| 177 | + return version, build |
| 178 | + |
| 179 | + |
| 180 | +def GetCodeSigningIdentifiers() -> Dict[str, str]: |
| 181 | + """Returns a dictionary of codesigning identifiers and their hashes""" |
| 182 | + XcodeVersion = GetXcodeVersion()[0] |
| 183 | + codeSignIDs = _GetCodeSignStringFromTerminal() |
| 184 | + |
| 185 | + identifiers = {} |
| 186 | + for codeSignID in (codeSignIDs or "").splitlines(): |
| 187 | + if "CSSMERR_TP_CERT_REVOKED" in codeSignID: |
| 188 | + continue |
| 189 | + if ")" not in codeSignID: |
| 190 | + continue |
| 191 | + if ((XcodeVersion >= 11 and "Apple Development" in codeSignID) |
| 192 | + or "Mac Developer" in codeSignID): |
| 193 | + identifier = codeSignID.split()[1] |
| 194 | + identifier_hash = re.search(r'\(.*?\)', codeSignID) |
| 195 | + if identifier_hash: |
| 196 | + identifier_hash = identifier_hash[0][1:-1] |
| 197 | + else: |
| 198 | + identifier_hash = None |
| 199 | + |
| 200 | + identifiers[identifier] = identifier_hash |
| 201 | + |
| 202 | + identifiers["-"] = None |
| 203 | + return identifiers |
| 204 | + |
| 205 | + |
| 206 | +def GetCodeSignID() -> str: |
| 207 | + """Return the first code signing identifier""" |
| 208 | + identifiers = GetCodeSigningIdentifiers() |
| 209 | + env_signing_id = os.environ.get('CODE_SIGN_ID') |
| 210 | + if env_signing_id: |
| 211 | + if env_signing_id in identifiers: |
| 212 | + return env_signing_id |
| 213 | + raise RuntimeError( |
| 214 | + f"Could not find environment specified identifier " |
| 215 | + f"{env_signing_id} in registered code signing identifiers") |
| 216 | + |
| 217 | + return list(GetCodeSigningIdentifiers().keys())[0] |
| 218 | + |
| 219 | + |
| 220 | +def GetDevelopmentTeamID(identifier=None): |
| 221 | + if "DEVELOPMENT_TEAM" in os.environ: |
| 222 | + return os.environ.get("DEVELOPMENT_TEAM") |
| 223 | + |
| 224 | + if not identifier: |
| 225 | + identifier = GetCodeSignID() |
| 226 | + if identifier == "-": |
| 227 | + return None |
171 | 228 |
|
172 | | - codeSignID = "-" |
173 | | - if os.environ.get('CODE_SIGN_ID'): |
174 | | - codeSignID = os.environ.get('CODE_SIGN_ID') |
175 | | - elif float(SDKVersion) >= 11.0 and \ |
176 | | - codeSignIDs.find(b'Apple Development') != -1: |
177 | | - codeSignID = "Apple Development" |
178 | | - elif codeSignIDs.find(b'Mac Developer') != -1: |
179 | | - codeSignID = "Mac Developer" |
180 | | - |
181 | | - for f in files: |
182 | | - subprocess.call(['codesign', '-f', '-s', '{codesignid}' |
183 | | - .format(codesignid=codeSignID), f], |
184 | | - stdout=devout, stderr=devout) |
185 | | - |
186 | | -def Codesign(install_path, verbose_output=False): |
| 229 | + identifier_hash = GetCodeSigningIdentifiers().get(identifier) |
| 230 | + if not identifier_hash: |
| 231 | + raise RuntimeError("Could not get identifiers hash") |
| 232 | + |
| 233 | + certs = subprocess.check_output( |
| 234 | + ["security", "find-certificate", "-c", identifier_hash, "-p"]) |
| 235 | + subject = GetCommandOutput(["openssl", "x509", "-subject"], input=certs) |
| 236 | + subject = subject.splitlines()[0] |
| 237 | + match = re.search("OU\s*=\s*(?P<team>([A-Za-z0-9_])+)", subject) |
| 238 | + if not match: |
| 239 | + raise RuntimeError("Could not parse the output " |
| 240 | + "certificate to find the team ID") |
| 241 | + |
| 242 | + groups = match.groupdict() |
| 243 | + team = groups.get("team") |
| 244 | + |
| 245 | + if not team: |
| 246 | + raise RuntimeError("Could not extract team id from certificate") |
| 247 | + |
| 248 | + return team |
| 249 | + |
| 250 | + |
| 251 | +def CodesignPath(path, identifier, team_identifier, |
| 252 | + force=False, is_framework=False) -> bool: |
| 253 | + resign = force |
| 254 | + if not force: |
| 255 | + codesigning_info = GetCommandOutput(["codesign", "-vd", path]) |
| 256 | + if not codesigning_info: |
| 257 | + resign = True |
| 258 | + else: |
| 259 | + # The output has multiple lines here |
| 260 | + for line in codesigning_info.splitlines(): |
| 261 | + if line.startswith("TeamIdentifier="): |
| 262 | + current_team_identifier = line.split("=")[-1] |
| 263 | + if (not current_team_identifier |
| 264 | + or "not set" in current_team_identifier): |
| 265 | + resign = True |
| 266 | + break |
| 267 | + elif current_team_identifier == team_identifier: |
| 268 | + break |
| 269 | + else: |
| 270 | + resign = True |
| 271 | + |
| 272 | + if not resign: |
| 273 | + return False |
| 274 | + |
| 275 | + # Frameworks need to be signed with different parameters than loose binaries |
| 276 | + if is_framework: |
| 277 | + subprocess.check_call( |
| 278 | + ["codesign", "--force", "--sign", identifier, |
| 279 | + "--generate-entitlement-der", "--verbose", path]) |
| 280 | + else: |
| 281 | + subprocess.check_call( |
| 282 | + ["codesign", "--force", "--sign", identifier, path], |
| 283 | + stdout=devout, stderr=devout) |
| 284 | + return True |
| 285 | + |
| 286 | + |
| 287 | +def Codesign(install_path, identifier=None, force=False, |
| 288 | + verbose_output=False) -> bool: |
187 | 289 | if not MacOS(): |
188 | 290 | return False |
| 291 | + |
| 292 | + identifier = identifier or GetCodeSignID() |
| 293 | + |
189 | 294 | if verbose_output: |
190 | 295 | global devout |
191 | 296 | devout = sys.stdout |
| 297 | + print(f"Code-signing files in {install_path} " |
| 298 | + f"with {identifier}", file=devout) |
192 | 299 |
|
193 | | - files = ExtractFilesRecursive(install_path, |
194 | | - (lambda file: '.so' in file or '.dylib' in file)) |
195 | | - CodesignFiles(files) |
| 300 | + try: |
| 301 | + team_identifier = GetDevelopmentTeamID(identifier) |
| 302 | + except: |
| 303 | + if verbose_output: |
| 304 | + print("Could not get team_identifier") |
| 305 | + team_identifier = None |
| 306 | + |
| 307 | + codesignPaths = [ |
| 308 | + os.path.join(install_path, 'lib'), |
| 309 | + os.path.join(install_path, 'plugin'), |
| 310 | + os.path.join(install_path, 'share/usd'), |
| 311 | + os.path.join(install_path, "frameworks") |
| 312 | + ] |
| 313 | + |
| 314 | + for basePath in codesignPaths: |
| 315 | + if not os.path.exists(basePath): |
| 316 | + continue |
| 317 | + |
| 318 | + for root, dirs, files in os.walk(basePath, topdown=True): |
| 319 | + for f in files: |
| 320 | + |
| 321 | + _, ext = os.path.splitext(f) |
| 322 | + if ext in (".dylib", ".so"): |
| 323 | + path = os.path.join(root, f) |
| 324 | + result = CodesignPath(path, identifier, |
| 325 | + team_identifier=team_identifier, |
| 326 | + force=force, is_framework=False) |
| 327 | + if verbose_output: |
| 328 | + if result: |
| 329 | + print(f"Code-signed binary: {path}") |
| 330 | + else: |
| 331 | + print(f"Did not code-sign binary: {path}") |
| 332 | + |
| 333 | + # Bit annoying to have to do this twice, but seems the fastest way |
| 334 | + # to skip traversing frameworks |
| 335 | + frameworks = [d for d in dirs if d.endswith(".framework")] |
| 336 | + dirs[:] = [d for d in dirs if not d.endswith(".framework")] |
| 337 | + |
| 338 | + for framework in frameworks: |
| 339 | + framework_name = os.path.splitext(framework)[0] |
| 340 | + if (framework_name.lower() not in |
| 341 | + ["openusd", "opensubdiv", "materialx"]): |
| 342 | + continue |
| 343 | + path = os.path.join(root, framework) |
| 344 | + result = CodesignPath(path, identifier, |
| 345 | + team_identifier=team_identifier, |
| 346 | + force=force, is_framework=True) |
| 347 | + if verbose_output: |
| 348 | + if result: |
| 349 | + print(f"Code-signed framework: {path}") |
| 350 | + else: |
| 351 | + print(f"Did not code-sign framework: {path}") |
| 352 | + |
| 353 | + return True |
196 | 354 |
|
197 | 355 | def CreateUniversalBinaries(context, libNames, x86Dir, armDir): |
198 | 356 | if not MacOS(): |
|
0 commit comments