You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							772 lines
						
					
					
						
							30 KiB
						
					
					
				
			
		
		
	
	
							772 lines
						
					
					
						
							30 KiB
						
					
					
				#!/usr/bin/env python3 | 
						|
# Generated by gyp. Do not edit. | 
						|
# Copyright (c) 2012 Google Inc. All rights reserved. | 
						|
# Use of this source code is governed by a BSD-style license that can be | 
						|
# found in the LICENSE file. | 
						|
 | 
						|
"""Utility functions to perform Xcode-style build steps. | 
						|
 | 
						|
These functions are executed via gyp-mac-tool when using the Makefile generator. | 
						|
""" | 
						|
 | 
						|
 | 
						|
import fcntl | 
						|
import fnmatch | 
						|
import glob | 
						|
import json | 
						|
import os | 
						|
import plistlib | 
						|
import re | 
						|
import shutil | 
						|
import struct | 
						|
import subprocess | 
						|
import sys | 
						|
import tempfile | 
						|
 | 
						|
 | 
						|
def main(args): | 
						|
    executor = MacTool() | 
						|
    exit_code = executor.Dispatch(args) | 
						|
    if exit_code is not None: | 
						|
        sys.exit(exit_code) | 
						|
 | 
						|
 | 
						|
class MacTool: | 
						|
    """This class performs all the Mac tooling steps. The methods can either be | 
						|
  executed directly, or dispatched from an argument list.""" | 
						|
 | 
						|
    def Dispatch(self, args): | 
						|
        """Dispatches a string command to a method.""" | 
						|
        if len(args) < 1: | 
						|
            raise Exception("Not enough arguments") | 
						|
 | 
						|
        method = "Exec%s" % self._CommandifyName(args[0]) | 
						|
        return getattr(self, method)(*args[1:]) | 
						|
 | 
						|
    def _CommandifyName(self, name_string): | 
						|
        """Transforms a tool name like copy-info-plist to CopyInfoPlist""" | 
						|
        return name_string.title().replace("-", "") | 
						|
 | 
						|
    def ExecCopyBundleResource(self, source, dest, convert_to_binary): | 
						|
        """Copies a resource file to the bundle/Resources directory, performing any | 
						|
    necessary compilation on each resource.""" | 
						|
        convert_to_binary = convert_to_binary == "True" | 
						|
        extension = os.path.splitext(source)[1].lower() | 
						|
        if os.path.isdir(source): | 
						|
            # Copy tree. | 
						|
            # TODO(thakis): This copies file attributes like mtime, while the | 
						|
            # single-file branch below doesn't. This should probably be changed to | 
						|
            # be consistent with the single-file branch. | 
						|
            if os.path.exists(dest): | 
						|
                shutil.rmtree(dest) | 
						|
            shutil.copytree(source, dest) | 
						|
        elif extension == ".xib": | 
						|
            return self._CopyXIBFile(source, dest) | 
						|
        elif extension == ".storyboard": | 
						|
            return self._CopyXIBFile(source, dest) | 
						|
        elif extension == ".strings" and not convert_to_binary: | 
						|
            self._CopyStringsFile(source, dest) | 
						|
        else: | 
						|
            if os.path.exists(dest): | 
						|
                os.unlink(dest) | 
						|
            shutil.copy(source, dest) | 
						|
 | 
						|
        if convert_to_binary and extension in (".plist", ".strings"): | 
						|
            self._ConvertToBinary(dest) | 
						|
 | 
						|
    def _CopyXIBFile(self, source, dest): | 
						|
        """Compiles a XIB file with ibtool into a binary plist in the bundle.""" | 
						|
 | 
						|
        # ibtool sometimes crashes with relative paths. See crbug.com/314728. | 
						|
        base = os.path.dirname(os.path.realpath(__file__)) | 
						|
        if os.path.relpath(source): | 
						|
            source = os.path.join(base, source) | 
						|
        if os.path.relpath(dest): | 
						|
            dest = os.path.join(base, dest) | 
						|
 | 
						|
        args = ["xcrun", "ibtool", "--errors", "--warnings", "--notices"] | 
						|
 | 
						|
        if os.environ["XCODE_VERSION_ACTUAL"] > "0700": | 
						|
            args.extend(["--auto-activate-custom-fonts"]) | 
						|
            if "IPHONEOS_DEPLOYMENT_TARGET" in os.environ: | 
						|
                args.extend( | 
						|
                    [ | 
						|
                        "--target-device", | 
						|
                        "iphone", | 
						|
                        "--target-device", | 
						|
                        "ipad", | 
						|
                        "--minimum-deployment-target", | 
						|
                        os.environ["IPHONEOS_DEPLOYMENT_TARGET"], | 
						|
                    ] | 
						|
                ) | 
						|
            else: | 
						|
                args.extend( | 
						|
                    [ | 
						|
                        "--target-device", | 
						|
                        "mac", | 
						|
                        "--minimum-deployment-target", | 
						|
                        os.environ["MACOSX_DEPLOYMENT_TARGET"], | 
						|
                    ] | 
						|
                ) | 
						|
 | 
						|
        args.extend( | 
						|
            ["--output-format", "human-readable-text", "--compile", dest, source] | 
						|
        ) | 
						|
 | 
						|
        ibtool_section_re = re.compile(r"/\*.*\*/") | 
						|
        ibtool_re = re.compile(r".*note:.*is clipping its content") | 
						|
        try: | 
						|
            stdout = subprocess.check_output(args) | 
						|
        except subprocess.CalledProcessError as e: | 
						|
            print(e.output) | 
						|
            raise | 
						|
        current_section_header = None | 
						|
        for line in stdout.splitlines(): | 
						|
            if ibtool_section_re.match(line): | 
						|
                current_section_header = line | 
						|
            elif not ibtool_re.match(line): | 
						|
                if current_section_header: | 
						|
                    print(current_section_header) | 
						|
                    current_section_header = None | 
						|
                print(line) | 
						|
        return 0 | 
						|
 | 
						|
    def _ConvertToBinary(self, dest): | 
						|
        subprocess.check_call( | 
						|
            ["xcrun", "plutil", "-convert", "binary1", "-o", dest, dest] | 
						|
        ) | 
						|
 | 
						|
    def _CopyStringsFile(self, source, dest): | 
						|
        """Copies a .strings file using iconv to reconvert the input into UTF-16.""" | 
						|
        input_code = self._DetectInputEncoding(source) or "UTF-8" | 
						|
 | 
						|
        # Xcode's CpyCopyStringsFile / builtin-copyStrings seems to call | 
						|
        # CFPropertyListCreateFromXMLData() behind the scenes; at least it prints | 
						|
        #     CFPropertyListCreateFromXMLData(): Old-style plist parser: missing | 
						|
        #     semicolon in dictionary. | 
						|
        # on invalid files. Do the same kind of validation. | 
						|
        import CoreFoundation | 
						|
 | 
						|
        with open(source, "rb") as in_file: | 
						|
            s = in_file.read() | 
						|
        d = CoreFoundation.CFDataCreate(None, s, len(s)) | 
						|
        _, error = CoreFoundation.CFPropertyListCreateFromXMLData(None, d, 0, None) | 
						|
        if error: | 
						|
            return | 
						|
 | 
						|
        with open(dest, "wb") as fp: | 
						|
            fp.write(s.decode(input_code).encode("UTF-16")) | 
						|
 | 
						|
    def _DetectInputEncoding(self, file_name): | 
						|
        """Reads the first few bytes from file_name and tries to guess the text | 
						|
    encoding. Returns None as a guess if it can't detect it.""" | 
						|
        with open(file_name, "rb") as fp: | 
						|
            try: | 
						|
                header = fp.read(3) | 
						|
            except Exception: | 
						|
                return None | 
						|
        if header.startswith(b"\xFE\xFF"): | 
						|
            return "UTF-16" | 
						|
        elif header.startswith(b"\xFF\xFE"): | 
						|
            return "UTF-16" | 
						|
        elif header.startswith(b"\xEF\xBB\xBF"): | 
						|
            return "UTF-8" | 
						|
        else: | 
						|
            return None | 
						|
 | 
						|
    def ExecCopyInfoPlist(self, source, dest, convert_to_binary, *keys): | 
						|
        """Copies the |source| Info.plist to the destination directory |dest|.""" | 
						|
        # Read the source Info.plist into memory. | 
						|
        with open(source) as fd: | 
						|
            lines = fd.read() | 
						|
 | 
						|
        # Insert synthesized key/value pairs (e.g. BuildMachineOSBuild). | 
						|
        plist = plistlib.readPlistFromString(lines) | 
						|
        if keys: | 
						|
            plist.update(json.loads(keys[0])) | 
						|
        lines = plistlib.writePlistToString(plist) | 
						|
 | 
						|
        # Go through all the environment variables and replace them as variables in | 
						|
        # the file. | 
						|
        IDENT_RE = re.compile(r"[_/\s]") | 
						|
        for key in os.environ: | 
						|
            if key.startswith("_"): | 
						|
                continue | 
						|
            evar = "${%s}" % key | 
						|
            evalue = os.environ[key] | 
						|
            lines = lines.replace(lines, evar, evalue) | 
						|
 | 
						|
            # Xcode supports various suffices on environment variables, which are | 
						|
            # all undocumented. :rfc1034identifier is used in the standard project | 
						|
            # template these days, and :identifier was used earlier. They are used to | 
						|
            # convert non-url characters into things that look like valid urls -- | 
						|
            # except that the replacement character for :identifier, '_' isn't valid | 
						|
            # in a URL either -- oops, hence :rfc1034identifier was born. | 
						|
            evar = "${%s:identifier}" % key | 
						|
            evalue = IDENT_RE.sub("_", os.environ[key]) | 
						|
            lines = lines.replace(lines, evar, evalue) | 
						|
 | 
						|
            evar = "${%s:rfc1034identifier}" % key | 
						|
            evalue = IDENT_RE.sub("-", os.environ[key]) | 
						|
            lines = lines.replace(lines, evar, evalue) | 
						|
 | 
						|
        # Remove any keys with values that haven't been replaced. | 
						|
        lines = lines.splitlines() | 
						|
        for i in range(len(lines)): | 
						|
            if lines[i].strip().startswith("<string>${"): | 
						|
                lines[i] = None | 
						|
                lines[i - 1] = None | 
						|
        lines = "\n".join(line for line in lines if line is not None) | 
						|
 | 
						|
        # Write out the file with variables replaced. | 
						|
        with open(dest, "w") as fd: | 
						|
            fd.write(lines) | 
						|
 | 
						|
        # Now write out PkgInfo file now that the Info.plist file has been | 
						|
        # "compiled". | 
						|
        self._WritePkgInfo(dest) | 
						|
 | 
						|
        if convert_to_binary == "True": | 
						|
            self._ConvertToBinary(dest) | 
						|
 | 
						|
    def _WritePkgInfo(self, info_plist): | 
						|
        """This writes the PkgInfo file from the data stored in Info.plist.""" | 
						|
        plist = plistlib.readPlist(info_plist) | 
						|
        if not plist: | 
						|
            return | 
						|
 | 
						|
        # Only create PkgInfo for executable types. | 
						|
        package_type = plist["CFBundlePackageType"] | 
						|
        if package_type != "APPL": | 
						|
            return | 
						|
 | 
						|
        # The format of PkgInfo is eight characters, representing the bundle type | 
						|
        # and bundle signature, each four characters. If that is missing, four | 
						|
        # '?' characters are used instead. | 
						|
        signature_code = plist.get("CFBundleSignature", "????") | 
						|
        if len(signature_code) != 4:  # Wrong length resets everything, too. | 
						|
            signature_code = "?" * 4 | 
						|
 | 
						|
        dest = os.path.join(os.path.dirname(info_plist), "PkgInfo") | 
						|
        with open(dest, "w") as fp: | 
						|
            fp.write(f"{package_type}{signature_code}") | 
						|
 | 
						|
    def ExecFlock(self, lockfile, *cmd_list): | 
						|
        """Emulates the most basic behavior of Linux's flock(1).""" | 
						|
        # Rely on exception handling to report errors. | 
						|
        fd = os.open(lockfile, os.O_RDONLY | os.O_NOCTTY | os.O_CREAT, 0o666) | 
						|
        fcntl.flock(fd, fcntl.LOCK_EX) | 
						|
        return subprocess.call(cmd_list) | 
						|
 | 
						|
    def ExecFilterLibtool(self, *cmd_list): | 
						|
        """Calls libtool and filters out '/path/to/libtool: file: foo.o has no | 
						|
    symbols'.""" | 
						|
        libtool_re = re.compile( | 
						|
            r"^.*libtool: (?:for architecture: \S* )?" r"file: .* has no symbols$" | 
						|
        ) | 
						|
        libtool_re5 = re.compile( | 
						|
            r"^.*libtool: warning for library: " | 
						|
            + r".* the table of contents is empty " | 
						|
            + r"\(no object file members in the library define global symbols\)$" | 
						|
        ) | 
						|
        env = os.environ.copy() | 
						|
        # Ref: | 
						|
        # http://www.opensource.apple.com/source/cctools/cctools-809/misc/libtool.c | 
						|
        # The problem with this flag is that it resets the file mtime on the file to | 
						|
        # epoch=0, e.g. 1970-1-1 or 1969-12-31 depending on timezone. | 
						|
        env["ZERO_AR_DATE"] = "1" | 
						|
        libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE, env=env) | 
						|
        err = libtoolout.communicate()[1].decode("utf-8") | 
						|
        for line in err.splitlines(): | 
						|
            if not libtool_re.match(line) and not libtool_re5.match(line): | 
						|
                print(line, file=sys.stderr) | 
						|
        # Unconditionally touch the output .a file on the command line if present | 
						|
        # and the command succeeded. A bit hacky. | 
						|
        if not libtoolout.returncode: | 
						|
            for i in range(len(cmd_list) - 1): | 
						|
                if cmd_list[i] == "-o" and cmd_list[i + 1].endswith(".a"): | 
						|
                    os.utime(cmd_list[i + 1], None) | 
						|
                    break | 
						|
        return libtoolout.returncode | 
						|
 | 
						|
    def ExecPackageIosFramework(self, framework): | 
						|
        # Find the name of the binary based on the part before the ".framework". | 
						|
        binary = os.path.basename(framework).split(".")[0] | 
						|
        module_path = os.path.join(framework, "Modules") | 
						|
        if not os.path.exists(module_path): | 
						|
            os.mkdir(module_path) | 
						|
        module_template = ( | 
						|
            "framework module %s {\n" | 
						|
            '  umbrella header "%s.h"\n' | 
						|
            "\n" | 
						|
            "  export *\n" | 
						|
            "  module * { export * }\n" | 
						|
            "}\n" % (binary, binary) | 
						|
        ) | 
						|
 | 
						|
        with open(os.path.join(module_path, "module.modulemap"), "w") as module_file: | 
						|
            module_file.write(module_template) | 
						|
 | 
						|
    def ExecPackageFramework(self, framework, version): | 
						|
        """Takes a path to Something.framework and the Current version of that and | 
						|
    sets up all the symlinks.""" | 
						|
        # Find the name of the binary based on the part before the ".framework". | 
						|
        binary = os.path.basename(framework).split(".")[0] | 
						|
 | 
						|
        CURRENT = "Current" | 
						|
        RESOURCES = "Resources" | 
						|
        VERSIONS = "Versions" | 
						|
 | 
						|
        if not os.path.exists(os.path.join(framework, VERSIONS, version, binary)): | 
						|
            # Binary-less frameworks don't seem to contain symlinks (see e.g. | 
						|
            # chromium's out/Debug/org.chromium.Chromium.manifest/ bundle). | 
						|
            return | 
						|
 | 
						|
        # Move into the framework directory to set the symlinks correctly. | 
						|
        pwd = os.getcwd() | 
						|
        os.chdir(framework) | 
						|
 | 
						|
        # Set up the Current version. | 
						|
        self._Relink(version, os.path.join(VERSIONS, CURRENT)) | 
						|
 | 
						|
        # Set up the root symlinks. | 
						|
        self._Relink(os.path.join(VERSIONS, CURRENT, binary), binary) | 
						|
        self._Relink(os.path.join(VERSIONS, CURRENT, RESOURCES), RESOURCES) | 
						|
 | 
						|
        # Back to where we were before! | 
						|
        os.chdir(pwd) | 
						|
 | 
						|
    def _Relink(self, dest, link): | 
						|
        """Creates a symlink to |dest| named |link|. If |link| already exists, | 
						|
    it is overwritten.""" | 
						|
        if os.path.lexists(link): | 
						|
            os.remove(link) | 
						|
        os.symlink(dest, link) | 
						|
 | 
						|
    def ExecCompileIosFrameworkHeaderMap(self, out, framework, *all_headers): | 
						|
        framework_name = os.path.basename(framework).split(".")[0] | 
						|
        all_headers = [os.path.abspath(header) for header in all_headers] | 
						|
        filelist = {} | 
						|
        for header in all_headers: | 
						|
            filename = os.path.basename(header) | 
						|
            filelist[filename] = header | 
						|
            filelist[os.path.join(framework_name, filename)] = header | 
						|
        WriteHmap(out, filelist) | 
						|
 | 
						|
    def ExecCopyIosFrameworkHeaders(self, framework, *copy_headers): | 
						|
        header_path = os.path.join(framework, "Headers") | 
						|
        if not os.path.exists(header_path): | 
						|
            os.makedirs(header_path) | 
						|
        for header in copy_headers: | 
						|
            shutil.copy(header, os.path.join(header_path, os.path.basename(header))) | 
						|
 | 
						|
    def ExecCompileXcassets(self, keys, *inputs): | 
						|
        """Compiles multiple .xcassets files into a single .car file. | 
						|
 | 
						|
    This invokes 'actool' to compile all the inputs .xcassets files. The | 
						|
    |keys| arguments is a json-encoded dictionary of extra arguments to | 
						|
    pass to 'actool' when the asset catalogs contains an application icon | 
						|
    or a launch image. | 
						|
 | 
						|
    Note that 'actool' does not create the Assets.car file if the asset | 
						|
    catalogs does not contains imageset. | 
						|
    """ | 
						|
        command_line = [ | 
						|
            "xcrun", | 
						|
            "actool", | 
						|
            "--output-format", | 
						|
            "human-readable-text", | 
						|
            "--compress-pngs", | 
						|
            "--notices", | 
						|
            "--warnings", | 
						|
            "--errors", | 
						|
        ] | 
						|
        is_iphone_target = "IPHONEOS_DEPLOYMENT_TARGET" in os.environ | 
						|
        if is_iphone_target: | 
						|
            platform = os.environ["CONFIGURATION"].split("-")[-1] | 
						|
            if platform not in ("iphoneos", "iphonesimulator"): | 
						|
                platform = "iphonesimulator" | 
						|
            command_line.extend( | 
						|
                [ | 
						|
                    "--platform", | 
						|
                    platform, | 
						|
                    "--target-device", | 
						|
                    "iphone", | 
						|
                    "--target-device", | 
						|
                    "ipad", | 
						|
                    "--minimum-deployment-target", | 
						|
                    os.environ["IPHONEOS_DEPLOYMENT_TARGET"], | 
						|
                    "--compile", | 
						|
                    os.path.abspath(os.environ["CONTENTS_FOLDER_PATH"]), | 
						|
                ] | 
						|
            ) | 
						|
        else: | 
						|
            command_line.extend( | 
						|
                [ | 
						|
                    "--platform", | 
						|
                    "macosx", | 
						|
                    "--target-device", | 
						|
                    "mac", | 
						|
                    "--minimum-deployment-target", | 
						|
                    os.environ["MACOSX_DEPLOYMENT_TARGET"], | 
						|
                    "--compile", | 
						|
                    os.path.abspath(os.environ["UNLOCALIZED_RESOURCES_FOLDER_PATH"]), | 
						|
                ] | 
						|
            ) | 
						|
        if keys: | 
						|
            keys = json.loads(keys) | 
						|
            for key, value in keys.items(): | 
						|
                arg_name = "--" + key | 
						|
                if isinstance(value, bool): | 
						|
                    if value: | 
						|
                        command_line.append(arg_name) | 
						|
                elif isinstance(value, list): | 
						|
                    for v in value: | 
						|
                        command_line.append(arg_name) | 
						|
                        command_line.append(str(v)) | 
						|
                else: | 
						|
                    command_line.append(arg_name) | 
						|
                    command_line.append(str(value)) | 
						|
        # Note: actool crashes if inputs path are relative, so use os.path.abspath | 
						|
        # to get absolute path name for inputs. | 
						|
        command_line.extend(map(os.path.abspath, inputs)) | 
						|
        subprocess.check_call(command_line) | 
						|
 | 
						|
    def ExecMergeInfoPlist(self, output, *inputs): | 
						|
        """Merge multiple .plist files into a single .plist file.""" | 
						|
        merged_plist = {} | 
						|
        for path in inputs: | 
						|
            plist = self._LoadPlistMaybeBinary(path) | 
						|
            self._MergePlist(merged_plist, plist) | 
						|
        plistlib.writePlist(merged_plist, output) | 
						|
 | 
						|
    def ExecCodeSignBundle(self, key, entitlements, provisioning, path, preserve): | 
						|
        """Code sign a bundle. | 
						|
 | 
						|
    This function tries to code sign an iOS bundle, following the same | 
						|
    algorithm as Xcode: | 
						|
      1. pick the provisioning profile that best match the bundle identifier, | 
						|
         and copy it into the bundle as embedded.mobileprovision, | 
						|
      2. copy Entitlements.plist from user or SDK next to the bundle, | 
						|
      3. code sign the bundle. | 
						|
    """ | 
						|
        substitutions, overrides = self._InstallProvisioningProfile( | 
						|
            provisioning, self._GetCFBundleIdentifier() | 
						|
        ) | 
						|
        entitlements_path = self._InstallEntitlements( | 
						|
            entitlements, substitutions, overrides | 
						|
        ) | 
						|
 | 
						|
        args = ["codesign", "--force", "--sign", key] | 
						|
        if preserve == "True": | 
						|
            args.extend(["--deep", "--preserve-metadata=identifier,entitlements"]) | 
						|
        else: | 
						|
            args.extend(["--entitlements", entitlements_path]) | 
						|
        args.extend(["--timestamp=none", path]) | 
						|
        subprocess.check_call(args) | 
						|
 | 
						|
    def _InstallProvisioningProfile(self, profile, bundle_identifier): | 
						|
        """Installs embedded.mobileprovision into the bundle. | 
						|
 | 
						|
    Args: | 
						|
      profile: string, optional, short name of the .mobileprovision file | 
						|
        to use, if empty or the file is missing, the best file installed | 
						|
        will be used | 
						|
      bundle_identifier: string, value of CFBundleIdentifier from Info.plist | 
						|
 | 
						|
    Returns: | 
						|
      A tuple containing two dictionary: variables substitutions and values | 
						|
      to overrides when generating the entitlements file. | 
						|
    """ | 
						|
        source_path, provisioning_data, team_id = self._FindProvisioningProfile( | 
						|
            profile, bundle_identifier | 
						|
        ) | 
						|
        target_path = os.path.join( | 
						|
            os.environ["BUILT_PRODUCTS_DIR"], | 
						|
            os.environ["CONTENTS_FOLDER_PATH"], | 
						|
            "embedded.mobileprovision", | 
						|
        ) | 
						|
        shutil.copy2(source_path, target_path) | 
						|
        substitutions = self._GetSubstitutions(bundle_identifier, team_id + ".") | 
						|
        return substitutions, provisioning_data["Entitlements"] | 
						|
 | 
						|
    def _FindProvisioningProfile(self, profile, bundle_identifier): | 
						|
        """Finds the .mobileprovision file to use for signing the bundle. | 
						|
 | 
						|
    Checks all the installed provisioning profiles (or if the user specified | 
						|
    the PROVISIONING_PROFILE variable, only consult it) and select the most | 
						|
    specific that correspond to the bundle identifier. | 
						|
 | 
						|
    Args: | 
						|
      profile: string, optional, short name of the .mobileprovision file | 
						|
        to use, if empty or the file is missing, the best file installed | 
						|
        will be used | 
						|
      bundle_identifier: string, value of CFBundleIdentifier from Info.plist | 
						|
 | 
						|
    Returns: | 
						|
      A tuple of the path to the selected provisioning profile, the data of | 
						|
      the embedded plist in the provisioning profile and the team identifier | 
						|
      to use for code signing. | 
						|
 | 
						|
    Raises: | 
						|
      SystemExit: if no .mobileprovision can be used to sign the bundle. | 
						|
    """ | 
						|
        profiles_dir = os.path.join( | 
						|
            os.environ["HOME"], "Library", "MobileDevice", "Provisioning Profiles" | 
						|
        ) | 
						|
        if not os.path.isdir(profiles_dir): | 
						|
            print( | 
						|
                "cannot find mobile provisioning for %s" % (bundle_identifier), | 
						|
                file=sys.stderr, | 
						|
            ) | 
						|
            sys.exit(1) | 
						|
        provisioning_profiles = None | 
						|
        if profile: | 
						|
            profile_path = os.path.join(profiles_dir, profile + ".mobileprovision") | 
						|
            if os.path.exists(profile_path): | 
						|
                provisioning_profiles = [profile_path] | 
						|
        if not provisioning_profiles: | 
						|
            provisioning_profiles = glob.glob( | 
						|
                os.path.join(profiles_dir, "*.mobileprovision") | 
						|
            ) | 
						|
        valid_provisioning_profiles = {} | 
						|
        for profile_path in provisioning_profiles: | 
						|
            profile_data = self._LoadProvisioningProfile(profile_path) | 
						|
            app_id_pattern = profile_data.get("Entitlements", {}).get( | 
						|
                "application-identifier", "" | 
						|
            ) | 
						|
            for team_identifier in profile_data.get("TeamIdentifier", []): | 
						|
                app_id = f"{team_identifier}.{bundle_identifier}" | 
						|
                if fnmatch.fnmatch(app_id, app_id_pattern): | 
						|
                    valid_provisioning_profiles[app_id_pattern] = ( | 
						|
                        profile_path, | 
						|
                        profile_data, | 
						|
                        team_identifier, | 
						|
                    ) | 
						|
        if not valid_provisioning_profiles: | 
						|
            print( | 
						|
                "cannot find mobile provisioning for %s" % (bundle_identifier), | 
						|
                file=sys.stderr, | 
						|
            ) | 
						|
            sys.exit(1) | 
						|
        # If the user has multiple provisioning profiles installed that can be | 
						|
        # used for ${bundle_identifier}, pick the most specific one (ie. the | 
						|
        # provisioning profile whose pattern is the longest). | 
						|
        selected_key = max(valid_provisioning_profiles, key=lambda v: len(v)) | 
						|
        return valid_provisioning_profiles[selected_key] | 
						|
 | 
						|
    def _LoadProvisioningProfile(self, profile_path): | 
						|
        """Extracts the plist embedded in a provisioning profile. | 
						|
 | 
						|
    Args: | 
						|
      profile_path: string, path to the .mobileprovision file | 
						|
 | 
						|
    Returns: | 
						|
      Content of the plist embedded in the provisioning profile as a dictionary. | 
						|
    """ | 
						|
        with tempfile.NamedTemporaryFile() as temp: | 
						|
            subprocess.check_call( | 
						|
                ["security", "cms", "-D", "-i", profile_path, "-o", temp.name] | 
						|
            ) | 
						|
            return self._LoadPlistMaybeBinary(temp.name) | 
						|
 | 
						|
    def _MergePlist(self, merged_plist, plist): | 
						|
        """Merge |plist| into |merged_plist|.""" | 
						|
        for key, value in plist.items(): | 
						|
            if isinstance(value, dict): | 
						|
                merged_value = merged_plist.get(key, {}) | 
						|
                if isinstance(merged_value, dict): | 
						|
                    self._MergePlist(merged_value, value) | 
						|
                    merged_plist[key] = merged_value | 
						|
                else: | 
						|
                    merged_plist[key] = value | 
						|
            else: | 
						|
                merged_plist[key] = value | 
						|
 | 
						|
    def _LoadPlistMaybeBinary(self, plist_path): | 
						|
        """Loads into a memory a plist possibly encoded in binary format. | 
						|
 | 
						|
    This is a wrapper around plistlib.readPlist that tries to convert the | 
						|
    plist to the XML format if it can't be parsed (assuming that it is in | 
						|
    the binary format). | 
						|
 | 
						|
    Args: | 
						|
      plist_path: string, path to a plist file, in XML or binary format | 
						|
 | 
						|
    Returns: | 
						|
      Content of the plist as a dictionary. | 
						|
    """ | 
						|
        try: | 
						|
            # First, try to read the file using plistlib that only supports XML, | 
						|
            # and if an exception is raised, convert a temporary copy to XML and | 
						|
            # load that copy. | 
						|
            return plistlib.readPlist(plist_path) | 
						|
        except Exception: | 
						|
            pass | 
						|
        with tempfile.NamedTemporaryFile() as temp: | 
						|
            shutil.copy2(plist_path, temp.name) | 
						|
            subprocess.check_call(["plutil", "-convert", "xml1", temp.name]) | 
						|
            return plistlib.readPlist(temp.name) | 
						|
 | 
						|
    def _GetSubstitutions(self, bundle_identifier, app_identifier_prefix): | 
						|
        """Constructs a dictionary of variable substitutions for Entitlements.plist. | 
						|
 | 
						|
    Args: | 
						|
      bundle_identifier: string, value of CFBundleIdentifier from Info.plist | 
						|
      app_identifier_prefix: string, value for AppIdentifierPrefix | 
						|
 | 
						|
    Returns: | 
						|
      Dictionary of substitutions to apply when generating Entitlements.plist. | 
						|
    """ | 
						|
        return { | 
						|
            "CFBundleIdentifier": bundle_identifier, | 
						|
            "AppIdentifierPrefix": app_identifier_prefix, | 
						|
        } | 
						|
 | 
						|
    def _GetCFBundleIdentifier(self): | 
						|
        """Extracts CFBundleIdentifier value from Info.plist in the bundle. | 
						|
 | 
						|
    Returns: | 
						|
      Value of CFBundleIdentifier in the Info.plist located in the bundle. | 
						|
    """ | 
						|
        info_plist_path = os.path.join( | 
						|
            os.environ["TARGET_BUILD_DIR"], os.environ["INFOPLIST_PATH"] | 
						|
        ) | 
						|
        info_plist_data = self._LoadPlistMaybeBinary(info_plist_path) | 
						|
        return info_plist_data["CFBundleIdentifier"] | 
						|
 | 
						|
    def _InstallEntitlements(self, entitlements, substitutions, overrides): | 
						|
        """Generates and install the ${BundleName}.xcent entitlements file. | 
						|
 | 
						|
    Expands variables "$(variable)" pattern in the source entitlements file, | 
						|
    add extra entitlements defined in the .mobileprovision file and the copy | 
						|
    the generated plist to "${BundlePath}.xcent". | 
						|
 | 
						|
    Args: | 
						|
      entitlements: string, optional, path to the Entitlements.plist template | 
						|
        to use, defaults to "${SDKROOT}/Entitlements.plist" | 
						|
      substitutions: dictionary, variable substitutions | 
						|
      overrides: dictionary, values to add to the entitlements | 
						|
 | 
						|
    Returns: | 
						|
      Path to the generated entitlements file. | 
						|
    """ | 
						|
        source_path = entitlements | 
						|
        target_path = os.path.join( | 
						|
            os.environ["BUILT_PRODUCTS_DIR"], os.environ["PRODUCT_NAME"] + ".xcent" | 
						|
        ) | 
						|
        if not source_path: | 
						|
            source_path = os.path.join(os.environ["SDKROOT"], "Entitlements.plist") | 
						|
        shutil.copy2(source_path, target_path) | 
						|
        data = self._LoadPlistMaybeBinary(target_path) | 
						|
        data = self._ExpandVariables(data, substitutions) | 
						|
        if overrides: | 
						|
            for key in overrides: | 
						|
                if key not in data: | 
						|
                    data[key] = overrides[key] | 
						|
        plistlib.writePlist(data, target_path) | 
						|
        return target_path | 
						|
 | 
						|
    def _ExpandVariables(self, data, substitutions): | 
						|
        """Expands variables "$(variable)" in data. | 
						|
 | 
						|
    Args: | 
						|
      data: object, can be either string, list or dictionary | 
						|
      substitutions: dictionary, variable substitutions to perform | 
						|
 | 
						|
    Returns: | 
						|
      Copy of data where each references to "$(variable)" has been replaced | 
						|
      by the corresponding value found in substitutions, or left intact if | 
						|
      the key was not found. | 
						|
    """ | 
						|
        if isinstance(data, str): | 
						|
            for key, value in substitutions.items(): | 
						|
                data = data.replace("$(%s)" % key, value) | 
						|
            return data | 
						|
        if isinstance(data, list): | 
						|
            return [self._ExpandVariables(v, substitutions) for v in data] | 
						|
        if isinstance(data, dict): | 
						|
            return {k: self._ExpandVariables(data[k], substitutions) for k in data} | 
						|
        return data | 
						|
 | 
						|
 | 
						|
def NextGreaterPowerOf2(x): | 
						|
    return 2 ** (x).bit_length() | 
						|
 | 
						|
 | 
						|
def WriteHmap(output_name, filelist): | 
						|
    """Generates a header map based on |filelist|. | 
						|
 | 
						|
  Per Mark Mentovai: | 
						|
    A header map is structured essentially as a hash table, keyed by names used | 
						|
    in #includes, and providing pathnames to the actual files. | 
						|
 | 
						|
  The implementation below and the comment above comes from inspecting: | 
						|
    http://www.opensource.apple.com/source/distcc/distcc-2503/distcc_dist/include_server/headermap.py?txt | 
						|
  while also looking at the implementation in clang in: | 
						|
    https://llvm.org/svn/llvm-project/cfe/trunk/lib/Lex/HeaderMap.cpp | 
						|
  """ | 
						|
    magic = 1751998832 | 
						|
    version = 1 | 
						|
    _reserved = 0 | 
						|
    count = len(filelist) | 
						|
    capacity = NextGreaterPowerOf2(count) | 
						|
    strings_offset = 24 + (12 * capacity) | 
						|
    max_value_length = max(len(value) for value in filelist.values()) | 
						|
 | 
						|
    out = open(output_name, "wb") | 
						|
    out.write( | 
						|
        struct.pack( | 
						|
            "<LHHLLLL", | 
						|
            magic, | 
						|
            version, | 
						|
            _reserved, | 
						|
            strings_offset, | 
						|
            count, | 
						|
            capacity, | 
						|
            max_value_length, | 
						|
        ) | 
						|
    ) | 
						|
 | 
						|
    # Create empty hashmap buckets. | 
						|
    buckets = [None] * capacity | 
						|
    for file, path in filelist.items(): | 
						|
        key = 0 | 
						|
        for c in file: | 
						|
            key += ord(c.lower()) * 13 | 
						|
 | 
						|
        # Fill next empty bucket. | 
						|
        while buckets[key & capacity - 1] is not None: | 
						|
            key = key + 1 | 
						|
        buckets[key & capacity - 1] = (file, path) | 
						|
 | 
						|
    next_offset = 1 | 
						|
    for bucket in buckets: | 
						|
        if bucket is None: | 
						|
            out.write(struct.pack("<LLL", 0, 0, 0)) | 
						|
        else: | 
						|
            (file, path) = bucket | 
						|
            key_offset = next_offset | 
						|
            prefix_offset = key_offset + len(file) + 1 | 
						|
            suffix_offset = prefix_offset + len(os.path.dirname(path) + os.sep) + 1 | 
						|
            next_offset = suffix_offset + len(os.path.basename(path)) + 1 | 
						|
            out.write(struct.pack("<LLL", key_offset, prefix_offset, suffix_offset)) | 
						|
 | 
						|
    # Pad byte since next offset starts at 1. | 
						|
    out.write(struct.pack("<x")) | 
						|
 | 
						|
    for bucket in buckets: | 
						|
        if bucket is not None: | 
						|
            (file, path) = bucket | 
						|
            out.write(struct.pack("<%ds" % len(file), file)) | 
						|
            out.write(struct.pack("<s", "\0")) | 
						|
            base = os.path.dirname(path) + os.sep | 
						|
            out.write(struct.pack("<%ds" % len(base), base)) | 
						|
            out.write(struct.pack("<s", "\0")) | 
						|
            path = os.path.basename(path) | 
						|
            out.write(struct.pack("<%ds" % len(path), path)) | 
						|
            out.write(struct.pack("<s", "\0")) | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__": | 
						|
    sys.exit(main(sys.argv[1:]))
 | 
						|
 |