Skip to main content
Glama
ipa_package_maker.py3.51 kB
# Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under both the MIT license found in the # LICENSE-MIT file in the root directory of this source tree and the Apache # License, Version 2.0 found in the LICENSE-APACHE file in the root directory # of this source tree. # pyre-strict import argparse import os import shutil import subprocess import tempfile from pathlib import Path from apple.tools.re_compatibility_utils.writable import make_dir_recursively_writable def _copy_ipa_contents(ipa_contents_dir: Path, output_dir: Path) -> None: if os.path.exists(output_dir): shutil.rmtree(output_dir, ignore_errors=False) shutil.copytree(ipa_contents_dir, output_dir, symlinks=True, dirs_exist_ok=False) def _delete_empty_SwiftSupport_dir(output_dir: Path) -> None: swiftSupportDir = output_dir / "SwiftSupport" if not swiftSupportDir.exists(): return swiftSupportDirHasFiles = False for _, _, files in os.walk(swiftSupportDir): if files: swiftSupportDirHasFiles = True break if not swiftSupportDirHasFiles: shutil.rmtree(swiftSupportDir) def _package_ipa_contents( ipa_contents_dir: Path, ipa_output_path: Path, compression_level: int, ) -> None: with tempfile.TemporaryDirectory() as processed_package_dir: processed_package_dir_path = Path(processed_package_dir) _copy_ipa_contents(ipa_contents_dir, processed_package_dir_path) # Apple requires SwiftSupport dir to be non-empty _delete_empty_SwiftSupport_dir(processed_package_dir_path) # Apple requires all executable files in an `.ipa` to be _writable_, otherwise App Store validation fails with: # "Asset validation failed (90711) Invalid executable permissions. The executable $X does not have its writable bit set." # Furthermore, there's additional internal infra that needs certain files to be writable. # # In normal development outside Meta, all files in an .ipa will be user writable, so let's just the sensible thing # and mirror behavior which Apple expects, so we're future-proof. make_dir_recursively_writable(str(processed_package_dir_path)) with open(ipa_output_path, "wb") as ipa_file: zip_cmd = ["zip", "-X", "-r", f"-{compression_level}", "-", "."] subprocess.run( zip_cmd, # .ipa zip file requires to be created relative to the package dir, # zip command has no way to express apart from changing cwd cwd=processed_package_dir_path, stdout=ipa_file, check=True, ) def main() -> None: parser = argparse.ArgumentParser(description="Tool to make an .ipa package file.") parser.add_argument( "--ipa-contents-dir", required=True, type=Path, help="The directory which has the contents of the .ipa file.", ) parser.add_argument( "--ipa-output-path", required=True, type=Path, help="The path to the output .ipa file.", ) parser.add_argument( "--compression-level", type=int, required=True, help="The compression level to use for 'zip'.", ) args = parser.parse_args() _package_ipa_contents( args.ipa_contents_dir, args.ipa_output_path, args.compression_level, ) if __name__ == "__main__": main()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server