source: trip-planner-front/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 29.7 KB
Line 
1#!/usr/bin/env python
2# Copyright (c) 2012 Google Inc. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Utility functions to perform Xcode-style build steps.
7
8These functions are executed via gyp-mac-tool when using the Makefile generator.
9"""
10
11from __future__ import print_function
12
13import fcntl
14import fnmatch
15import glob
16import json
17import os
18import plistlib
19import re
20import shutil
21import struct
22import subprocess
23import sys
24import tempfile
25
26PY3 = bytes != str
27
28
29def main(args):
30 executor = MacTool()
31 exit_code = executor.Dispatch(args)
32 if exit_code is not None:
33 sys.exit(exit_code)
34
35
36class MacTool(object):
37 """This class performs all the Mac tooling steps. The methods can either be
38 executed directly, or dispatched from an argument list."""
39
40 def Dispatch(self, args):
41 """Dispatches a string command to a method."""
42 if len(args) < 1:
43 raise Exception("Not enough arguments")
44
45 method = "Exec%s" % self._CommandifyName(args[0])
46 return getattr(self, method)(*args[1:])
47
48 def _CommandifyName(self, name_string):
49 """Transforms a tool name like copy-info-plist to CopyInfoPlist"""
50 return name_string.title().replace("-", "")
51
52 def ExecCopyBundleResource(self, source, dest, convert_to_binary):
53 """Copies a resource file to the bundle/Resources directory, performing any
54 necessary compilation on each resource."""
55 convert_to_binary = convert_to_binary == "True"
56 extension = os.path.splitext(source)[1].lower()
57 if os.path.isdir(source):
58 # Copy tree.
59 # TODO(thakis): This copies file attributes like mtime, while the
60 # single-file branch below doesn't. This should probably be changed to
61 # be consistent with the single-file branch.
62 if os.path.exists(dest):
63 shutil.rmtree(dest)
64 shutil.copytree(source, dest)
65 elif extension == ".xib":
66 return self._CopyXIBFile(source, dest)
67 elif extension == ".storyboard":
68 return self._CopyXIBFile(source, dest)
69 elif extension == ".strings" and not convert_to_binary:
70 self._CopyStringsFile(source, dest)
71 else:
72 if os.path.exists(dest):
73 os.unlink(dest)
74 shutil.copy(source, dest)
75
76 if convert_to_binary and extension in (".plist", ".strings"):
77 self._ConvertToBinary(dest)
78
79 def _CopyXIBFile(self, source, dest):
80 """Compiles a XIB file with ibtool into a binary plist in the bundle."""
81
82 # ibtool sometimes crashes with relative paths. See crbug.com/314728.
83 base = os.path.dirname(os.path.realpath(__file__))
84 if os.path.relpath(source):
85 source = os.path.join(base, source)
86 if os.path.relpath(dest):
87 dest = os.path.join(base, dest)
88
89 args = ["xcrun", "ibtool", "--errors", "--warnings", "--notices"]
90
91 if os.environ["XCODE_VERSION_ACTUAL"] > "0700":
92 args.extend(["--auto-activate-custom-fonts"])
93 if "IPHONEOS_DEPLOYMENT_TARGET" in os.environ:
94 args.extend(
95 [
96 "--target-device",
97 "iphone",
98 "--target-device",
99 "ipad",
100 "--minimum-deployment-target",
101 os.environ["IPHONEOS_DEPLOYMENT_TARGET"],
102 ]
103 )
104 else:
105 args.extend(
106 [
107 "--target-device",
108 "mac",
109 "--minimum-deployment-target",
110 os.environ["MACOSX_DEPLOYMENT_TARGET"],
111 ]
112 )
113
114 args.extend(
115 ["--output-format", "human-readable-text", "--compile", dest, source]
116 )
117
118 ibtool_section_re = re.compile(r"/\*.*\*/")
119 ibtool_re = re.compile(r".*note:.*is clipping its content")
120 try:
121 stdout = subprocess.check_output(args)
122 except subprocess.CalledProcessError as e:
123 print(e.output)
124 raise
125 current_section_header = None
126 for line in stdout.splitlines():
127 if ibtool_section_re.match(line):
128 current_section_header = line
129 elif not ibtool_re.match(line):
130 if current_section_header:
131 print(current_section_header)
132 current_section_header = None
133 print(line)
134 return 0
135
136 def _ConvertToBinary(self, dest):
137 subprocess.check_call(
138 ["xcrun", "plutil", "-convert", "binary1", "-o", dest, dest]
139 )
140
141 def _CopyStringsFile(self, source, dest):
142 """Copies a .strings file using iconv to reconvert the input into UTF-16."""
143 input_code = self._DetectInputEncoding(source) or "UTF-8"
144
145 # Xcode's CpyCopyStringsFile / builtin-copyStrings seems to call
146 # CFPropertyListCreateFromXMLData() behind the scenes; at least it prints
147 # CFPropertyListCreateFromXMLData(): Old-style plist parser: missing
148 # semicolon in dictionary.
149 # on invalid files. Do the same kind of validation.
150 import CoreFoundation
151
152 with open(source, "rb") as in_file:
153 s = in_file.read()
154 d = CoreFoundation.CFDataCreate(None, s, len(s))
155 _, error = CoreFoundation.CFPropertyListCreateFromXMLData(None, d, 0, None)
156 if error:
157 return
158
159 with open(dest, "wb") as fp:
160 fp.write(s.decode(input_code).encode("UTF-16"))
161
162 def _DetectInputEncoding(self, file_name):
163 """Reads the first few bytes from file_name and tries to guess the text
164 encoding. Returns None as a guess if it can't detect it."""
165 with open(file_name, "rb") as fp:
166 try:
167 header = fp.read(3)
168 except Exception:
169 return None
170 if header.startswith(b"\xFE\xFF"):
171 return "UTF-16"
172 elif header.startswith(b"\xFF\xFE"):
173 return "UTF-16"
174 elif header.startswith(b"\xEF\xBB\xBF"):
175 return "UTF-8"
176 else:
177 return None
178
179 def ExecCopyInfoPlist(self, source, dest, convert_to_binary, *keys):
180 """Copies the |source| Info.plist to the destination directory |dest|."""
181 # Read the source Info.plist into memory.
182 with open(source, "r") as fd:
183 lines = fd.read()
184
185 # Insert synthesized key/value pairs (e.g. BuildMachineOSBuild).
186 plist = plistlib.readPlistFromString(lines)
187 if keys:
188 plist.update(json.loads(keys[0]))
189 lines = plistlib.writePlistToString(plist)
190
191 # Go through all the environment variables and replace them as variables in
192 # the file.
193 IDENT_RE = re.compile(r"[_/\s]")
194 for key in os.environ:
195 if key.startswith("_"):
196 continue
197 evar = "${%s}" % key
198 evalue = os.environ[key]
199 lines = lines.replace(lines, evar, evalue)
200
201 # Xcode supports various suffices on environment variables, which are
202 # all undocumented. :rfc1034identifier is used in the standard project
203 # template these days, and :identifier was used earlier. They are used to
204 # convert non-url characters into things that look like valid urls --
205 # except that the replacement character for :identifier, '_' isn't valid
206 # in a URL either -- oops, hence :rfc1034identifier was born.
207 evar = "${%s:identifier}" % key
208 evalue = IDENT_RE.sub("_", os.environ[key])
209 lines = lines.replace(lines, evar, evalue)
210
211 evar = "${%s:rfc1034identifier}" % key
212 evalue = IDENT_RE.sub("-", os.environ[key])
213 lines = lines.replace(lines, evar, evalue)
214
215 # Remove any keys with values that haven't been replaced.
216 lines = lines.splitlines()
217 for i in range(len(lines)):
218 if lines[i].strip().startswith("<string>${"):
219 lines[i] = None
220 lines[i - 1] = None
221 lines = "\n".join(line for line in lines if line is not None)
222
223 # Write out the file with variables replaced.
224 with open(dest, "w") as fd:
225 fd.write(lines)
226
227 # Now write out PkgInfo file now that the Info.plist file has been
228 # "compiled".
229 self._WritePkgInfo(dest)
230
231 if convert_to_binary == "True":
232 self._ConvertToBinary(dest)
233
234 def _WritePkgInfo(self, info_plist):
235 """This writes the PkgInfo file from the data stored in Info.plist."""
236 plist = plistlib.readPlist(info_plist)
237 if not plist:
238 return
239
240 # Only create PkgInfo for executable types.
241 package_type = plist["CFBundlePackageType"]
242 if package_type != "APPL":
243 return
244
245 # The format of PkgInfo is eight characters, representing the bundle type
246 # and bundle signature, each four characters. If that is missing, four
247 # '?' characters are used instead.
248 signature_code = plist.get("CFBundleSignature", "????")
249 if len(signature_code) != 4: # Wrong length resets everything, too.
250 signature_code = "?" * 4
251
252 dest = os.path.join(os.path.dirname(info_plist), "PkgInfo")
253 with open(dest, "w") as fp:
254 fp.write("%s%s" % (package_type, signature_code))
255
256 def ExecFlock(self, lockfile, *cmd_list):
257 """Emulates the most basic behavior of Linux's flock(1)."""
258 # Rely on exception handling to report errors.
259 fd = os.open(lockfile, os.O_RDONLY | os.O_NOCTTY | os.O_CREAT, 0o666)
260 fcntl.flock(fd, fcntl.LOCK_EX)
261 return subprocess.call(cmd_list)
262
263 def ExecFilterLibtool(self, *cmd_list):
264 """Calls libtool and filters out '/path/to/libtool: file: foo.o has no
265 symbols'."""
266 libtool_re = re.compile(
267 r"^.*libtool: (?:for architecture: \S* )?" r"file: .* has no symbols$"
268 )
269 libtool_re5 = re.compile(
270 r"^.*libtool: warning for library: "
271 + r".* the table of contents is empty "
272 + r"\(no object file members in the library define global symbols\)$"
273 )
274 env = os.environ.copy()
275 # Ref:
276 # http://www.opensource.apple.com/source/cctools/cctools-809/misc/libtool.c
277 # The problem with this flag is that it resets the file mtime on the file to
278 # epoch=0, e.g. 1970-1-1 or 1969-12-31 depending on timezone.
279 env["ZERO_AR_DATE"] = "1"
280 libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE, env=env)
281 _, err = libtoolout.communicate()
282 if PY3:
283 err = err.decode("utf-8")
284 for line in err.splitlines():
285 if not libtool_re.match(line) and not libtool_re5.match(line):
286 print(line, file=sys.stderr)
287 # Unconditionally touch the output .a file on the command line if present
288 # and the command succeeded. A bit hacky.
289 if not libtoolout.returncode:
290 for i in range(len(cmd_list) - 1):
291 if cmd_list[i] == "-o" and cmd_list[i + 1].endswith(".a"):
292 os.utime(cmd_list[i + 1], None)
293 break
294 return libtoolout.returncode
295
296 def ExecPackageIosFramework(self, framework):
297 # Find the name of the binary based on the part before the ".framework".
298 binary = os.path.basename(framework).split(".")[0]
299 module_path = os.path.join(framework, "Modules")
300 if not os.path.exists(module_path):
301 os.mkdir(module_path)
302 module_template = (
303 "framework module %s {\n"
304 ' umbrella header "%s.h"\n'
305 "\n"
306 " export *\n"
307 " module * { export * }\n"
308 "}\n" % (binary, binary)
309 )
310
311 with open(os.path.join(module_path, "module.modulemap"), "w") as module_file:
312 module_file.write(module_template)
313
314 def ExecPackageFramework(self, framework, version):
315 """Takes a path to Something.framework and the Current version of that and
316 sets up all the symlinks."""
317 # Find the name of the binary based on the part before the ".framework".
318 binary = os.path.basename(framework).split(".")[0]
319
320 CURRENT = "Current"
321 RESOURCES = "Resources"
322 VERSIONS = "Versions"
323
324 if not os.path.exists(os.path.join(framework, VERSIONS, version, binary)):
325 # Binary-less frameworks don't seem to contain symlinks (see e.g.
326 # chromium's out/Debug/org.chromium.Chromium.manifest/ bundle).
327 return
328
329 # Move into the framework directory to set the symlinks correctly.
330 pwd = os.getcwd()
331 os.chdir(framework)
332
333 # Set up the Current version.
334 self._Relink(version, os.path.join(VERSIONS, CURRENT))
335
336 # Set up the root symlinks.
337 self._Relink(os.path.join(VERSIONS, CURRENT, binary), binary)
338 self._Relink(os.path.join(VERSIONS, CURRENT, RESOURCES), RESOURCES)
339
340 # Back to where we were before!
341 os.chdir(pwd)
342
343 def _Relink(self, dest, link):
344 """Creates a symlink to |dest| named |link|. If |link| already exists,
345 it is overwritten."""
346 if os.path.lexists(link):
347 os.remove(link)
348 os.symlink(dest, link)
349
350 def ExecCompileIosFrameworkHeaderMap(self, out, framework, *all_headers):
351 framework_name = os.path.basename(framework).split(".")[0]
352 all_headers = [os.path.abspath(header) for header in all_headers]
353 filelist = {}
354 for header in all_headers:
355 filename = os.path.basename(header)
356 filelist[filename] = header
357 filelist[os.path.join(framework_name, filename)] = header
358 WriteHmap(out, filelist)
359
360 def ExecCopyIosFrameworkHeaders(self, framework, *copy_headers):
361 header_path = os.path.join(framework, "Headers")
362 if not os.path.exists(header_path):
363 os.makedirs(header_path)
364 for header in copy_headers:
365 shutil.copy(header, os.path.join(header_path, os.path.basename(header)))
366
367 def ExecCompileXcassets(self, keys, *inputs):
368 """Compiles multiple .xcassets files into a single .car file.
369
370 This invokes 'actool' to compile all the inputs .xcassets files. The
371 |keys| arguments is a json-encoded dictionary of extra arguments to
372 pass to 'actool' when the asset catalogs contains an application icon
373 or a launch image.
374
375 Note that 'actool' does not create the Assets.car file if the asset
376 catalogs does not contains imageset.
377 """
378 command_line = [
379 "xcrun",
380 "actool",
381 "--output-format",
382 "human-readable-text",
383 "--compress-pngs",
384 "--notices",
385 "--warnings",
386 "--errors",
387 ]
388 is_iphone_target = "IPHONEOS_DEPLOYMENT_TARGET" in os.environ
389 if is_iphone_target:
390 platform = os.environ["CONFIGURATION"].split("-")[-1]
391 if platform not in ("iphoneos", "iphonesimulator"):
392 platform = "iphonesimulator"
393 command_line.extend(
394 [
395 "--platform",
396 platform,
397 "--target-device",
398 "iphone",
399 "--target-device",
400 "ipad",
401 "--minimum-deployment-target",
402 os.environ["IPHONEOS_DEPLOYMENT_TARGET"],
403 "--compile",
404 os.path.abspath(os.environ["CONTENTS_FOLDER_PATH"]),
405 ]
406 )
407 else:
408 command_line.extend(
409 [
410 "--platform",
411 "macosx",
412 "--target-device",
413 "mac",
414 "--minimum-deployment-target",
415 os.environ["MACOSX_DEPLOYMENT_TARGET"],
416 "--compile",
417 os.path.abspath(os.environ["UNLOCALIZED_RESOURCES_FOLDER_PATH"]),
418 ]
419 )
420 if keys:
421 keys = json.loads(keys)
422 for key, value in keys.items():
423 arg_name = "--" + key
424 if isinstance(value, bool):
425 if value:
426 command_line.append(arg_name)
427 elif isinstance(value, list):
428 for v in value:
429 command_line.append(arg_name)
430 command_line.append(str(v))
431 else:
432 command_line.append(arg_name)
433 command_line.append(str(value))
434 # Note: actool crashes if inputs path are relative, so use os.path.abspath
435 # to get absolute path name for inputs.
436 command_line.extend(map(os.path.abspath, inputs))
437 subprocess.check_call(command_line)
438
439 def ExecMergeInfoPlist(self, output, *inputs):
440 """Merge multiple .plist files into a single .plist file."""
441 merged_plist = {}
442 for path in inputs:
443 plist = self._LoadPlistMaybeBinary(path)
444 self._MergePlist(merged_plist, plist)
445 plistlib.writePlist(merged_plist, output)
446
447 def ExecCodeSignBundle(self, key, entitlements, provisioning, path, preserve):
448 """Code sign a bundle.
449
450 This function tries to code sign an iOS bundle, following the same
451 algorithm as Xcode:
452 1. pick the provisioning profile that best match the bundle identifier,
453 and copy it into the bundle as embedded.mobileprovision,
454 2. copy Entitlements.plist from user or SDK next to the bundle,
455 3. code sign the bundle.
456 """
457 substitutions, overrides = self._InstallProvisioningProfile(
458 provisioning, self._GetCFBundleIdentifier()
459 )
460 entitlements_path = self._InstallEntitlements(
461 entitlements, substitutions, overrides
462 )
463
464 args = ["codesign", "--force", "--sign", key]
465 if preserve == "True":
466 args.extend(["--deep", "--preserve-metadata=identifier,entitlements"])
467 else:
468 args.extend(["--entitlements", entitlements_path])
469 args.extend(["--timestamp=none", path])
470 subprocess.check_call(args)
471
472 def _InstallProvisioningProfile(self, profile, bundle_identifier):
473 """Installs embedded.mobileprovision into the bundle.
474
475 Args:
476 profile: string, optional, short name of the .mobileprovision file
477 to use, if empty or the file is missing, the best file installed
478 will be used
479 bundle_identifier: string, value of CFBundleIdentifier from Info.plist
480
481 Returns:
482 A tuple containing two dictionary: variables substitutions and values
483 to overrides when generating the entitlements file.
484 """
485 source_path, provisioning_data, team_id = self._FindProvisioningProfile(
486 profile, bundle_identifier
487 )
488 target_path = os.path.join(
489 os.environ["BUILT_PRODUCTS_DIR"],
490 os.environ["CONTENTS_FOLDER_PATH"],
491 "embedded.mobileprovision",
492 )
493 shutil.copy2(source_path, target_path)
494 substitutions = self._GetSubstitutions(bundle_identifier, team_id + ".")
495 return substitutions, provisioning_data["Entitlements"]
496
497 def _FindProvisioningProfile(self, profile, bundle_identifier):
498 """Finds the .mobileprovision file to use for signing the bundle.
499
500 Checks all the installed provisioning profiles (or if the user specified
501 the PROVISIONING_PROFILE variable, only consult it) and select the most
502 specific that correspond to the bundle identifier.
503
504 Args:
505 profile: string, optional, short name of the .mobileprovision file
506 to use, if empty or the file is missing, the best file installed
507 will be used
508 bundle_identifier: string, value of CFBundleIdentifier from Info.plist
509
510 Returns:
511 A tuple of the path to the selected provisioning profile, the data of
512 the embedded plist in the provisioning profile and the team identifier
513 to use for code signing.
514
515 Raises:
516 SystemExit: if no .mobileprovision can be used to sign the bundle.
517 """
518 profiles_dir = os.path.join(
519 os.environ["HOME"], "Library", "MobileDevice", "Provisioning Profiles"
520 )
521 if not os.path.isdir(profiles_dir):
522 print(
523 "cannot find mobile provisioning for %s" % (bundle_identifier),
524 file=sys.stderr,
525 )
526 sys.exit(1)
527 provisioning_profiles = None
528 if profile:
529 profile_path = os.path.join(profiles_dir, profile + ".mobileprovision")
530 if os.path.exists(profile_path):
531 provisioning_profiles = [profile_path]
532 if not provisioning_profiles:
533 provisioning_profiles = glob.glob(
534 os.path.join(profiles_dir, "*.mobileprovision")
535 )
536 valid_provisioning_profiles = {}
537 for profile_path in provisioning_profiles:
538 profile_data = self._LoadProvisioningProfile(profile_path)
539 app_id_pattern = profile_data.get("Entitlements", {}).get(
540 "application-identifier", ""
541 )
542 for team_identifier in profile_data.get("TeamIdentifier", []):
543 app_id = "%s.%s" % (team_identifier, bundle_identifier)
544 if fnmatch.fnmatch(app_id, app_id_pattern):
545 valid_provisioning_profiles[app_id_pattern] = (
546 profile_path,
547 profile_data,
548 team_identifier,
549 )
550 if not valid_provisioning_profiles:
551 print(
552 "cannot find mobile provisioning for %s" % (bundle_identifier),
553 file=sys.stderr,
554 )
555 sys.exit(1)
556 # If the user has multiple provisioning profiles installed that can be
557 # used for ${bundle_identifier}, pick the most specific one (ie. the
558 # provisioning profile whose pattern is the longest).
559 selected_key = max(valid_provisioning_profiles, key=lambda v: len(v))
560 return valid_provisioning_profiles[selected_key]
561
562 def _LoadProvisioningProfile(self, profile_path):
563 """Extracts the plist embedded in a provisioning profile.
564
565 Args:
566 profile_path: string, path to the .mobileprovision file
567
568 Returns:
569 Content of the plist embedded in the provisioning profile as a dictionary.
570 """
571 with tempfile.NamedTemporaryFile() as temp:
572 subprocess.check_call(
573 ["security", "cms", "-D", "-i", profile_path, "-o", temp.name]
574 )
575 return self._LoadPlistMaybeBinary(temp.name)
576
577 def _MergePlist(self, merged_plist, plist):
578 """Merge |plist| into |merged_plist|."""
579 for key, value in plist.items():
580 if isinstance(value, dict):
581 merged_value = merged_plist.get(key, {})
582 if isinstance(merged_value, dict):
583 self._MergePlist(merged_value, value)
584 merged_plist[key] = merged_value
585 else:
586 merged_plist[key] = value
587 else:
588 merged_plist[key] = value
589
590 def _LoadPlistMaybeBinary(self, plist_path):
591 """Loads into a memory a plist possibly encoded in binary format.
592
593 This is a wrapper around plistlib.readPlist that tries to convert the
594 plist to the XML format if it can't be parsed (assuming that it is in
595 the binary format).
596
597 Args:
598 plist_path: string, path to a plist file, in XML or binary format
599
600 Returns:
601 Content of the plist as a dictionary.
602 """
603 try:
604 # First, try to read the file using plistlib that only supports XML,
605 # and if an exception is raised, convert a temporary copy to XML and
606 # load that copy.
607 return plistlib.readPlist(plist_path)
608 except Exception:
609 pass
610 with tempfile.NamedTemporaryFile() as temp:
611 shutil.copy2(plist_path, temp.name)
612 subprocess.check_call(["plutil", "-convert", "xml1", temp.name])
613 return plistlib.readPlist(temp.name)
614
615 def _GetSubstitutions(self, bundle_identifier, app_identifier_prefix):
616 """Constructs a dictionary of variable substitutions for Entitlements.plist.
617
618 Args:
619 bundle_identifier: string, value of CFBundleIdentifier from Info.plist
620 app_identifier_prefix: string, value for AppIdentifierPrefix
621
622 Returns:
623 Dictionary of substitutions to apply when generating Entitlements.plist.
624 """
625 return {
626 "CFBundleIdentifier": bundle_identifier,
627 "AppIdentifierPrefix": app_identifier_prefix,
628 }
629
630 def _GetCFBundleIdentifier(self):
631 """Extracts CFBundleIdentifier value from Info.plist in the bundle.
632
633 Returns:
634 Value of CFBundleIdentifier in the Info.plist located in the bundle.
635 """
636 info_plist_path = os.path.join(
637 os.environ["TARGET_BUILD_DIR"], os.environ["INFOPLIST_PATH"]
638 )
639 info_plist_data = self._LoadPlistMaybeBinary(info_plist_path)
640 return info_plist_data["CFBundleIdentifier"]
641
642 def _InstallEntitlements(self, entitlements, substitutions, overrides):
643 """Generates and install the ${BundleName}.xcent entitlements file.
644
645 Expands variables "$(variable)" pattern in the source entitlements file,
646 add extra entitlements defined in the .mobileprovision file and the copy
647 the generated plist to "${BundlePath}.xcent".
648
649 Args:
650 entitlements: string, optional, path to the Entitlements.plist template
651 to use, defaults to "${SDKROOT}/Entitlements.plist"
652 substitutions: dictionary, variable substitutions
653 overrides: dictionary, values to add to the entitlements
654
655 Returns:
656 Path to the generated entitlements file.
657 """
658 source_path = entitlements
659 target_path = os.path.join(
660 os.environ["BUILT_PRODUCTS_DIR"], os.environ["PRODUCT_NAME"] + ".xcent"
661 )
662 if not source_path:
663 source_path = os.path.join(os.environ["SDKROOT"], "Entitlements.plist")
664 shutil.copy2(source_path, target_path)
665 data = self._LoadPlistMaybeBinary(target_path)
666 data = self._ExpandVariables(data, substitutions)
667 if overrides:
668 for key in overrides:
669 if key not in data:
670 data[key] = overrides[key]
671 plistlib.writePlist(data, target_path)
672 return target_path
673
674 def _ExpandVariables(self, data, substitutions):
675 """Expands variables "$(variable)" in data.
676
677 Args:
678 data: object, can be either string, list or dictionary
679 substitutions: dictionary, variable substitutions to perform
680
681 Returns:
682 Copy of data where each references to "$(variable)" has been replaced
683 by the corresponding value found in substitutions, or left intact if
684 the key was not found.
685 """
686 if isinstance(data, str):
687 for key, value in substitutions.items():
688 data = data.replace("$(%s)" % key, value)
689 return data
690 if isinstance(data, list):
691 return [self._ExpandVariables(v, substitutions) for v in data]
692 if isinstance(data, dict):
693 return {k: self._ExpandVariables(data[k], substitutions) for k in data}
694 return data
695
696
697def NextGreaterPowerOf2(x):
698 return 2 ** (x).bit_length()
699
700
701def WriteHmap(output_name, filelist):
702 """Generates a header map based on |filelist|.
703
704 Per Mark Mentovai:
705 A header map is structured essentially as a hash table, keyed by names used
706 in #includes, and providing pathnames to the actual files.
707
708 The implementation below and the comment above comes from inspecting:
709 http://www.opensource.apple.com/source/distcc/distcc-2503/distcc_dist/include_server/headermap.py?txt
710 while also looking at the implementation in clang in:
711 https://llvm.org/svn/llvm-project/cfe/trunk/lib/Lex/HeaderMap.cpp
712 """
713 magic = 1751998832
714 version = 1
715 _reserved = 0
716 count = len(filelist)
717 capacity = NextGreaterPowerOf2(count)
718 strings_offset = 24 + (12 * capacity)
719 max_value_length = max(len(value) for value in filelist.values())
720
721 out = open(output_name, "wb")
722 out.write(
723 struct.pack(
724 "<LHHLLLL",
725 magic,
726 version,
727 _reserved,
728 strings_offset,
729 count,
730 capacity,
731 max_value_length,
732 )
733 )
734
735 # Create empty hashmap buckets.
736 buckets = [None] * capacity
737 for file, path in filelist.items():
738 key = 0
739 for c in file:
740 key += ord(c.lower()) * 13
741
742 # Fill next empty bucket.
743 while buckets[key & capacity - 1] is not None:
744 key = key + 1
745 buckets[key & capacity - 1] = (file, path)
746
747 next_offset = 1
748 for bucket in buckets:
749 if bucket is None:
750 out.write(struct.pack("<LLL", 0, 0, 0))
751 else:
752 (file, path) = bucket
753 key_offset = next_offset
754 prefix_offset = key_offset + len(file) + 1
755 suffix_offset = prefix_offset + len(os.path.dirname(path) + os.sep) + 1
756 next_offset = suffix_offset + len(os.path.basename(path)) + 1
757 out.write(struct.pack("<LLL", key_offset, prefix_offset, suffix_offset))
758
759 # Pad byte since next offset starts at 1.
760 out.write(struct.pack("<x"))
761
762 for bucket in buckets:
763 if bucket is not None:
764 (file, path) = bucket
765 out.write(struct.pack("<%ds" % len(file), file))
766 out.write(struct.pack("<s", "\0"))
767 base = os.path.dirname(path) + os.sep
768 out.write(struct.pack("<%ds" % len(base), base))
769 out.write(struct.pack("<s", "\0"))
770 path = os.path.basename(path)
771 out.write(struct.pack("<%ds" % len(path), path))
772 out.write(struct.pack("<s", "\0"))
773
774
775if __name__ == "__main__":
776 sys.exit(main(sys.argv[1:]))
Note: See TracBrowser for help on using the repository browser.