예제 #1
0
파일: checkout.py 프로젝트: schwa/punic
    def prepare(self):

        if self.config.use_submodules:
            relative_checkout_path = self.checkout_path.relative_to(self.config.root_path)

            result = runner.run('git submodule status "{}"'.format(relative_checkout_path))
            if result.return_code == 0:
                match = re.match(r'^(?P<flag> |\-|\+|U)(?P<sha>[a-f0-9]+) (?P<path>.+)( \((?P<description>.+)\))?', result.stdout)
                flag = match.groupdict()['flag']
                if flag == ' ':
                    pass
                elif flag == '-':
                    raise Exception('Uninitialized submodule {}. Please report this!'.format(self.checkout_path))
                elif flag == '+':
                    raise Exception('Submodule {} doesn\'t match expected revision'.format(self.checkout_path))
                elif flag == 'U':
                    raise Exception('Submodule {} has merge conflicts'.format(self.checkout_path))
            else:
                if self.checkout_path.exists():
                    raise Exception('Want to create a submodule in {} but something already exists in there.'.format(self.checkout_path))
                logging.debug('Adding submodule for {}'.format(self))
                runner.check_run(['git', 'submodule', 'add', '--force', self.identifier.remote_url, self.checkout_path.relative_to(self.config.root_path)])

            # runner.check_run(['git', 'submodule', 'add', '--force', self.identifier.remote_url, self.checkout_path.relative_to(self.config.root_path)])
            # runner.check_run(['git', 'submodule', 'update', self.checkout_path.relative_to(self.config.root_path)])

            logging.debug('Updating {}'.format(self))
            self.repository.checkout(self.revision)
        else:

            # TODO: This isn't really 'fetch'
            if self.config.fetch:

                self.repository.checkout(self.revision)
                logging.debug('<sub>Copying project to <ref>Carthage/Checkouts</ref></sub>')
                if self.checkout_path.exists():
                    shutil.rmtree(self.checkout_path, ignore_errors=True)
                shutil.copytree(self.repository.path, self.checkout_path, symlinks=True, ignore=shutil.ignore_patterns('.git'))

        if not self.checkout_path.exists():
            raise Exception('No checkout at path: {}'.format(self.checkout_path))

        # We only need to bother making a symlink to <root>/Carthage/Build if dependency also has dependencies.
        if len(self.punic.dependencies_for_project_and_tag(self.identifier, self.revision)):
            # Make a Carthage/Build symlink inside checked out project.
            carthage_path = self.checkout_path / 'Carthage'
            if not carthage_path.exists():
                carthage_path.mkdir()

            carthage_symlink_path = carthage_path / 'Build'
            if carthage_symlink_path.exists():
                if carthage_symlink_path.is_dir():
                    shutil.rmtree(str(carthage_symlink_path))
                else:
                    carthage_symlink_path.unlink()
            logging.debug('<sub>Creating symlink: <ref>{}</ref> to <ref>{}</ref></sub>'.format(carthage_symlink_path.relative_to(self.config.root_path), self.config.build_path.relative_to(self.config.root_path)))
            assert self.config.build_path.exists()

            # TODO: Generate this programatically.
            os.symlink("../../../Build", str(carthage_symlink_path))
예제 #2
0
파일: model.py 프로젝트: lholoubek/punic
    def prepare(self):

        # TODO: This isn't really 'can_fetch'
        if self.punic.config.can_fetch:
            self.repository.checkout(self.revision)
            logger.debug('<sub>Copying project to <ref>Carthage/Checkouts</ref></sub>')
            if self.checkout_path.exists():
                shutil.rmtree(self.checkout_path)
            shutil.copytree(self.repository.path, self.checkout_path, ignore=shutil.ignore_patterns('.git'))

        if not self.checkout_path.exists():
            raise Exception('No checkout at path: {}'.format(self.checkout_path))

        # We only need to bother making a symlink to <root>/Carthage/Build if dependency also has dependencies.
        if len(self.punic.dependencies_for_project_and_tag(self.identifier, self.revision)):
            # Make a Carthage/Build symlink inside checked out project.
            carthage_path = self.checkout_path / 'Carthage'
            if not carthage_path.exists():
                carthage_path.mkdir()

            carthage_symlink_path = carthage_path / 'Build'
            if carthage_symlink_path.exists():
                carthage_symlink_path.unlink()
            logger.debug('<sub>Creating symlink: <ref>{}</ref> to <ref>{}</ref></sub>'.format(
                carthage_symlink_path.relative_to(self.punic.config.root_path), self.punic.config.build_path.relative_to(self.punic.config.root_path)))
            assert self.punic.config.build_path.exists()
            os.symlink(str(self.punic.config.build_path), str(carthage_symlink_path))
예제 #3
0
    def _post_process(self, platform, products):

        ########################################################################################################

        logger.debug("<sub>Post processing</sub>...")

        # By convention sdk[0] is always the device sdk (e.g. 'iphoneos' and not 'iphonesimulator')
        device_sdk = platform.device_sdk
        device_product = products[device_sdk]

        ########################################################################################################

        output_product = copy(device_product)
        output_product.target_build_dir = self.config.build_path / platform.output_directory_name

        ########################################################################################################

        logger.debug('<sub>Copying binary</sub>...')
        if output_product.product_path.exists():
            shutil.rmtree(output_product.product_path)
        shutil.copytree(device_product.product_path, output_product.product_path)

        ########################################################################################################

        if len(products) > 1:
            logger.debug('<sub>Lipo-ing</sub>...')
            executable_paths = [product.executable_path for product in products.values()]
            command = ['/usr/bin/xcrun', 'lipo', '-create'] + executable_paths + ['-output', output_product.executable_path]
            runner.check_run(command)
            mtime = executable_paths[0].stat().st_mtime
            os.utime(str(output_product.executable_path), (mtime, mtime))

        ########################################################################################################

        logger.debug('<sub>Copying swiftmodule files</sub>...')
        for product in products.values():
            for path in product.module_paths:
                relative_path = path.relative_to(product.product_path)
                shutil.copyfile(path, output_product.product_path / relative_path)

        ########################################################################################################

        logger.debug('<sub>Copying bcsymbolmap files</sub>...')
        for product in products.values():
            for path in product.bcsymbolmap_paths:
                shutil.copy(path, output_product.target_build_dir)

        ########################################################################################################

        logger.debug('<sub>Producing dSYM files</sub>...')
        command = ['/usr/bin/xcrun', 'dsymutil', str(output_product.executable_path), '-o', str(output_product.target_build_dir / (output_product.executable_name + '.dSYM'))]
        runner.check_run(command)

        ########################################################################################################
예제 #4
0
파일: model.py 프로젝트: lholoubek/punic
    def _post_process(self, platform, products):

        ########################################################################################################

        logger.debug("<sub>Post processing</sub>")

        # By convention sdk[0] is always the device sdk (e.g. 'iphoneos' and not 'iphonesimulator')
        device_sdk = platform.device_sdk
        device_product = products[device_sdk]

        ########################################################################################################

        output_product = copy(device_product)
        output_product.target_build_dir = self.config.build_path / platform.output_directory_name

        ########################################################################################################

        logger.debug('<sub>Copying binary</sub>')
        if output_product.product_path.exists():
            shutil.rmtree(output_product.product_path)
        shutil.copytree(device_product.product_path, output_product.product_path)

        ########################################################################################################

        if len(products) > 1:
            logger.debug('<sub>Lipo-ing</sub>')
            executable_paths = [product.executable_path for product in products.values()]
            command = ['/usr/bin/xcrun', 'lipo', '-create'] + executable_paths + ['-output',
                output_product.executable_path]
            runner.check_run(command)
            mtime = executable_paths[0].stat().st_mtime
            os.utime(str(output_product.executable_path), (mtime, mtime))

        ########################################################################################################

        logger.debug('<sub>Copying swiftmodule files</sub>')
        for product in products.values():
            for path in product.module_paths:
                relative_path = path.relative_to(product.product_path)
                shutil.copyfile(path, output_product.product_path / relative_path)

        ########################################################################################################

        logger.debug('<sub>Copying bcsymbolmap files</sub>')
        for product in products.values():
            for path in product.bcsymbolmap_paths:
                shutil.copy(path, output_product.target_build_dir)

        ########################################################################################################

        logger.debug('<sub>Producing dSYM files</sub>')
        command = ['/usr/bin/xcrun', 'dsymutil', str(output_product.executable_path), '-o',
            str(output_product.target_build_dir / (output_product.executable_name + '.dSYM'))]
        runner.check_run(command)
예제 #5
0
def test_update_and_build():
    if quick_tests_only:
        return

    source = Path(__file__).parent / 'Examples'
    destination = Path(tempfile.mkdtemp()) / 'Examples'

    shutil.copytree(source, destination)

    project_paths = [path for path in destination.iterdir() if path.is_dir()]

    for project_path in project_paths:

        with work_directory(project_path):

            output = runner.check_run('punic update')
예제 #6
0
def test_update_and_build():
    if quick_tests_only:
        return

    source = Path(__file__).parent / 'Examples'
    destination = Path(tempfile.mkdtemp()) / 'Examples'

    shutil.copytree(source, destination)

    project_paths = [path for path in destination.iterdir() if path.is_dir()]

    for project_path in project_paths:

        with work_directory(project_path):

            output = runner.check_run('punic update')
예제 #7
0
def copy_frameworks_main():
    sym_root = Path(os.environ['SYMROOT'])
    valid_architectures = set(os.environ['VALID_ARCHS'].split(' '))
    input_file_count = int(os.environ['SCRIPT_INPUT_FILE_COUNT'])
    input_files = [
        Path(os.environ.get('SCRIPT_INPUT_FILE_{}'.format(index)))
        for index in range(0, input_file_count)
    ]
    expanded_identity = os.environ['EXPANDED_CODE_SIGN_IDENTITY_NAME']
    built_products_dir = Path(os.environ['BUILT_PRODUCTS_DIR'])
    frameworks_folder_path = os.environ['FRAMEWORKS_FOLDER_PATH']
    frameworks_path = built_products_dir / frameworks_folder_path
    code_signing_allowed = os.environ['CODE_SIGNING_ALLOWED'] == 'YES'
    enable_bitcode = os.environ['ENABLE_BITCODE'] == 'YES'
    project_dir = Path(os.environ['PROJECT_DIR'])
    platform_display_name = os.environ['PLATFORM_DISPLAY_NAME']
    punic_builds_dir = project_dir / 'Carthage' / 'Build' / platform_display_name
    action = os.environ['ACTION']

    for input_path in input_files:

        logger.info('Processing: "{}"'.format(input_path.name))

        # We don't modify the input frameworks but rather the ones in the built products directory
        output_path = frameworks_path / input_path.name

        framework_name = input_path.stem

        logger.info('\tCopying framework "{}" to "$SYMROOT/{}"'.format(
            framework_name, output_path.relative_to(sym_root)))
        if output_path.exists():
            shutil.rmtree(output_path)

        def ignore(src, names):
            src = Path(src)
            if src.suffix == ".framework":
                return ['Headers', 'PrivateHeaders', 'Modules']
            else:
                return []

        shutil.copytree(input_path, output_path, ignore=ignore)

        framework_path = output_path

        binary_path = framework_path / framework_name

        if code_signing_allowed:
            # Find out what architectures the framework has
            output = runner.check_call(
                ['/usr/bin/xcrun', 'lipo', '-info', binary_path])
            match = re.match(
                r'^Architectures in the fat file: (.+) are: (.+)'.format(
                    binary_path), output)
            assert match.groups()[0] == str(binary_path)
            architectures = set(match.groups()[1].strip().split(' '))
            logger.info('\tArchitectures: {}'.format(list(architectures)))

            # Produce a list of architectures that are not valid
            excluded_architectures = architectures.difference(
                valid_architectures)

            # Skip if all architectures are valid
            if not excluded_architectures:
                continue

            # For each invalid architecture strip it from framework
            for architecture in excluded_architectures:
                logger.info('\tStripping "{}" from "{}"'.format(
                    architecture, framework_name))
                output = runner.check_call([
                    '/usr/bin/xcrun', 'lipo', '-remove', architecture,
                    '-output', binary_path, binary_path
                ])

                # Resign framework
                logger.info('\tResigning "{}"/"{}" with "{}"'.format(
                    framework_name, architecture, expanded_identity))

            logger.info('\tCode signing: "$SYMROOT/{}"'.format(
                binary_path.relative_to(sym_root)))

            # noinspection PyUnusedLocal
            result = runner.check_call([
                '/usr/bin/xcrun', 'codesign', '--force', '--sign',
                expanded_identity,
                '--preserve-metadata=identifier,entitlements', binary_path
            ])
        else:
            logger.info('\tCode signing not allowed. Skipping.')

        if action == 'install':
            uuids = uuids_from_binary(binary_path)

            # Copy dSYM files from $PROJECT_DIRCarthage/Build to $BUILT_PRODUCTS_DIR
            dsym_path = input_path.parent / (binary_path.name + '.dSYM')

            if dsym_path.exists():

                dsym_output_path = built_products_dir / dsym_path.name

                logger.info(
                    '\tCopying "$PROJECT_DIR/{}" to "$BUILT_PRODUCTS_DIR"'.
                    format(dsym_path.relative_to(project_dir)))
                if dsym_output_path.exists():
                    shutil.rmtree(dsym_output_path)
                shutil.copytree(dsym_path, dsym_output_path)

            # Copy bcsymbolmap files from $PROJECT_DIRCarthage/Build to $BUILT_PRODUCTS_DIR
            if enable_bitcode:
                for uuid in uuids:
                    bcsymbolmap_path = punic_builds_dir / (uuid +
                                                           '.bcsymbolmap')
                    logger.info(
                        '\tCopying "$PROJECT_DIR/{}" to "$BUILT_PRODUCTS_DIR"'.
                        format(bcsymbolmap_path.relative_to(project_dir)))
                    shutil.copy(bcsymbolmap_path, built_products_dir)
예제 #8
0
def copy_frameworks_main():

    sym_root = Path(os.environ['SYMROOT'])
    valid_architectures = set(os.environ['VALID_ARCHS'].split(' '))
    input_file_count = int(os.environ['SCRIPT_INPUT_FILE_COUNT'])
    input_files = [Path(os.environ.get('SCRIPT_INPUT_FILE_{}'.format(index))) for index in range(0, input_file_count)]
    expanded_identity = os.environ['EXPANDED_CODE_SIGN_IDENTITY_NAME']
    built_products_dir = Path(os.environ['BUILT_PRODUCTS_DIR'])
    frameworks_folder_path = os.environ['FRAMEWORKS_FOLDER_PATH']
    frameworks_path = built_products_dir / frameworks_folder_path
    code_signing_allowed = os.environ['CODE_SIGNING_ALLOWED'] == 'YES'
    enable_bitcode = os.environ['ENABLE_BITCODE'] == 'YES'
    project_dir = Path(os.environ['PROJECT_DIR'])
    platform_display_name = os.environ['PLATFORM_DISPLAY_NAME']
    punic_builds_dir = project_dir / 'Carthage' / 'Build' / platform_display_name
    action = os.environ['ACTION']

    for input_path in input_files:

        logger.info('Processing: "{}"'.format(input_path.name))

        # We don't modify the input frameworks but rather the ones in the built products directory
        output_path = frameworks_path / input_path.name

        framework_name = input_path.stem

        logger.info('\tCopying framework "{}" to "$SYMROOT/{}"'.format(framework_name, output_path.relative_to(sym_root)))
        if output_path.exists():
            shutil.rmtree(output_path)
        shutil.copytree(input_path, output_path)

        framework_path = output_path

        binary_path = framework_path / framework_name

        if code_signing_allowed:
            # Find out what architectures the framework has
            output = runner.check_call(['/usr/bin/xcrun', 'lipo', '-info', binary_path])
            match = re.match(r'^Architectures in the fat file: (.+) are: (.+)'.format(binary_path), output)
            assert match.groups()[0] == str(binary_path)
            architectures = set(match.groups()[1].strip().split(' '))
            logger.info('\tArchitectures: {}'.format(list(architectures)))

            # Produce a list of architectures that are not valid
            excluded_architectures = architectures.difference(valid_architectures)

            # Skip if all architectures are valid
            if not excluded_architectures:
                continue

            # For each invalid architecture strip it from framework
            for architecture in excluded_architectures:
                logger.info('\tStripping "{}" from "{}"'.format(architecture, framework_name))
                output = runner.check_call(['/usr/bin/xcrun', 'lipo', '-remove', architecture, '-output', binary_path, binary_path])

                # Resign framework
                logger.info('\tResigning "{}"/"{}" with "{}"'.format(framework_name, architecture, expanded_identity))

            logger.info('\tCode signing: "$SYMROOT/{}"'.format(binary_path.relative_to(sym_root)))

            # noinspection PyUnusedLocal
            result = runner.check_call(['/usr/bin/xcrun', 'codesign', '--force', '--sign', expanded_identity,
                '--preserve-metadata=identifier,entitlements', binary_path])
        else:
            logger.info('\tCode signing not allowed. Skipping.')

        if action == 'install':
            uuids = uuids_from_binary(binary_path)

            # Copy dSYM files from $PROJECT_DIRCarthage/Build to $BUILT_PRODUCTS_DIR
            dsym_path = input_path.parent / (binary_path.name + '.dSYM')

            if dsym_path.exists():

                dsym_output_path = built_products_dir / dsym_path.name

                logger.info('\tCopying "$PROJECT_DIR/{}" to "$BUILT_PRODUCTS_DIR"'.format(dsym_path.relative_to(project_dir)))
                if dsym_output_path.exists():
                    shutil.rmtree(dsym_output_path)
                shutil.copytree(dsym_path, dsym_output_path)


            # Copy bcsymbolmap files from $PROJECT_DIRCarthage/Build to $BUILT_PRODUCTS_DIR
            if enable_bitcode:
                for uuid in uuids:
                    bcsymbolmap_path = punic_builds_dir / (uuid + '.bcsymbolmap')
                    logger.info('\tCopying "$PROJECT_DIR/{}" to "$BUILT_PRODUCTS_DIR"'.format(bcsymbolmap_path.relative_to(project_dir)))
                    shutil.copy(bcsymbolmap_path, built_products_dir)
예제 #9
0
    def _post_process(self, platform, products):
        # type: (punic.platform.Platform, List)

        ########################################################################################################

        logging.debug("<sub>Post processing</sub>...")

        # TODO: QUESTION: Is it possible that this could mix targets with different SDKs?
        products_by_name_then_sdk = defaultdict(dict)
        for product in products:
            products_by_name_then_sdk[product.full_product_name][
                product.sdk] = product

        for products_by_sdk in products_by_name_then_sdk.values():

            products = products_by_sdk.values()

            # TODO: By convention sdk[0] is always the device sdk (e.g. 'iphoneos' and not 'iphonesimulator')
            primary_sdk = platform.sdks[0]

            device_product = products_by_sdk[primary_sdk]

            ########################################################################################################

            output_product = copy(device_product)
            output_product.target_build_dir = self.config.build_path / platform.output_directory_name

            ########################################################################################################

            logging.debug('<sub>Copying binary</sub>...')
            if output_product.product_path.exists():
                shutil.rmtree(output_product.product_path)

            if not device_product.product_path.exists():
                raise Exception("No product at: {}".format(
                    device_product.product_path))

            shutil.copytree(device_product.product_path,
                            output_product.product_path,
                            symlinks=True)

            ########################################################################################################

            if len(products) > 1:
                logging.debug('<sub>Lipo-ing</sub>...')
                executable_paths = [
                    product.executable_path for product in products
                ]
                command = ['/usr/bin/xcrun', 'lipo', '-create'
                           ] + executable_paths + [
                               '-output', output_product.executable_path
                           ]
                runner.check_run(command)
                mtime = executable_paths[0].stat().st_mtime
                os.utime(str(output_product.executable_path), (mtime, mtime))

            ########################################################################################################

            logging.debug('<sub>Copying swiftmodule files</sub>...')
            for product in products:
                for path in product.module_paths:
                    relative_path = path.relative_to(product.product_path)
                    shutil.copyfile(
                        path, output_product.product_path / relative_path)

            ########################################################################################################

            logging.debug('<sub>Copying bcsymbolmap files</sub>...')
            for product in products:
                for path in product.bcsymbolmap_paths:
                    shutil.copy(path, output_product.target_build_dir)

            ########################################################################################################

            logging.debug('<sub>Producing dSYM files</sub>...')
            command = [
                '/usr/bin/xcrun', 'dsymutil',
                str(output_product.executable_path), '-o',
                str(output_product.target_build_dir /
                    (output_product.executable_name + '.dSYM'))
            ]
            runner.check_run(command)
예제 #10
0
    def prepare(self):

        if self.config.use_submodules:
            relative_checkout_path = self.checkout_path.relative_to(
                self.config.root_path)

            result = runner.run(
                'git submodule status "{}"'.format(relative_checkout_path))
            if result.return_code == 0:
                match = re.match(
                    r'^(?P<flag> |\-|\+|U)(?P<sha>[a-f0-9]+) (?P<path>.+)( \((?P<description>.+)\))?',
                    result.stdout)
                flag = match.groupdict()['flag']
                if flag == ' ':
                    pass
                elif flag == '-':
                    raise Exception(
                        'Uninitialized submodule {}. Please report this!'.
                        format(self.checkout_path))
                elif flag == '+':
                    raise Exception(
                        'Submodule {} doesn\'t match expected revision'.format(
                            self.checkout_path))
                elif flag == 'U':
                    raise Exception('Submodule {} has merge conflicts'.format(
                        self.checkout_path))
            else:
                if self.checkout_path.exists():
                    raise Exception(
                        'Want to create a submodule in {} but something already exists in there.'
                        .format(self.checkout_path))
                logging.debug('Adding submodule for {}'.format(self))
                runner.check_run([
                    'git', 'submodule', 'add', '--force',
                    self.identifier.remote_url,
                    self.checkout_path.relative_to(self.config.root_path)
                ])

            # runner.check_run(['git', 'submodule', 'add', '--force', self.identifier.remote_url, self.checkout_path.relative_to(self.config.root_path)])
            # runner.check_run(['git', 'submodule', 'update', self.checkout_path.relative_to(self.config.root_path)])

            logging.debug('Updating {}'.format(self))
            self.repository.checkout(self.revision)
        else:

            # TODO: This isn't really 'fetch'
            if self.config.fetch:

                self.repository.checkout(self.revision)
                logging.debug(
                    '<sub>Copying project to <ref>Carthage/Checkouts</ref></sub>'
                )
                if self.checkout_path.exists():
                    shutil.rmtree(self.checkout_path, ignore_errors=True)
                shutil.copytree(self.repository.path,
                                self.checkout_path,
                                symlinks=True,
                                ignore=shutil.ignore_patterns('.git'))

        if not self.checkout_path.exists():
            raise Exception('No checkout at path: {}'.format(
                self.checkout_path))

        # We only need to bother making a symlink to <root>/Carthage/Build if dependency also has dependencies.
        if len(
                self.punic.dependencies_for_project_and_tag(
                    self.identifier, self.revision)):
            # Make a Carthage/Build symlink inside checked out project.
            carthage_path = self.checkout_path / 'Carthage'
            if not carthage_path.exists():
                carthage_path.mkdir()

            carthage_symlink_path = carthage_path / 'Build'
            if carthage_symlink_path.exists():
                carthage_symlink_path.unlink()
            logging.debug(
                '<sub>Creating symlink: <ref>{}</ref> to <ref>{}</ref></sub>'.
                format(
                    carthage_symlink_path.relative_to(self.config.root_path),
                    self.config.build_path.relative_to(self.config.root_path)))
            assert self.config.build_path.exists()

            # TODO: Generate this programatically.
            os.symlink("../../../Build", str(carthage_symlink_path))
예제 #11
0
파일: __init__.py 프로젝트: schwa/punic
    def _post_process(self, platform, products):
        # type: (punic.platform.Platform, List)

        ########################################################################################################

        logging.debug("<sub>Post processing</sub>...")

        # TODO: QUESTION: Is it possible that this could mix targets with different SDKs?
        products_by_name_then_sdk = defaultdict(dict)
        for product in products:
            products_by_name_then_sdk[product.full_product_name][product.sdk] = product


        for products_by_sdk in products_by_name_then_sdk.values():

            products = products_by_sdk.values()

            # TODO: By convention sdk[0] is always the device sdk (e.g. 'iphoneos' and not 'iphonesimulator')
            primary_sdk = platform.sdks[0]

            device_product = products_by_sdk[primary_sdk]

            ########################################################################################################

            output_product = copy(device_product)
            output_product.target_build_dir = self.config.build_path / platform.output_directory_name

            ########################################################################################################

            logging.debug('<sub>Copying binary</sub>...')
            if output_product.product_path.exists():
                shutil.rmtree(output_product.product_path)

            if not device_product.product_path.exists():
                raise Exception("No product at: {}".format(device_product.product_path))

            shutil.copytree(device_product.product_path, output_product.product_path, symlinks=True)

            ########################################################################################################

            if len(products) > 1:
                logging.debug('<sub>Lipo-ing</sub>...')
                executable_paths = [product.executable_path for product in products]
                command = ['/usr/bin/xcrun', 'lipo', '-create'] + executable_paths + ['-output', output_product.executable_path]
                runner.check_run(command)
                mtime = executable_paths[0].stat().st_mtime
                os.utime(str(output_product.executable_path), (mtime, mtime))

            ########################################################################################################

            logging.debug('<sub>Copying swiftmodule files</sub>...')
            for product in products:
                for path in product.module_paths:
                    relative_path = path.relative_to(product.product_path)
                    shutil.copyfile(path, output_product.product_path / relative_path)

            ########################################################################################################

            logging.debug('<sub>Copying bcsymbolmap files</sub>...')
            for product in products:
                for path in product.bcsymbolmap_paths:
                    shutil.copy(path, output_product.target_build_dir)

            ########################################################################################################

            logging.debug('<sub>Producing dSYM files</sub>...')
            command = ['/usr/bin/xcrun', 'dsymutil', str(output_product.executable_path), '-o', str(output_product.target_build_dir / (output_product.executable_name + '.dSYM'))]
            runner.check_run(command)

            ########################################################################################################