Created
October 4, 2024 09:45
-
-
Save btn0s/04b5f0096f6ca81e26a67af5014122ff to your computer and use it in GitHub Desktop.
generate-swift-package.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| import xml.etree.ElementTree as ET | |
| import re | |
| import glob | |
| import plistlib | |
| import json | |
| def parse_pbxproj(pbxproj_path): | |
| """ | |
| Parse the Xcode project file (.pbxproj) to extract target names, source files, frameworks, and build settings. | |
| """ | |
| try: | |
| with open(pbxproj_path, 'r') as file: | |
| content = file.read() | |
| plist = plistlib.loads(bytes(content, 'UTF-8')) | |
| except Exception as e: | |
| print(f"Error reading pbxproj file: {e}") | |
| return [], [], [], {} | |
| targets = [] | |
| source_files = [] | |
| frameworks = [] | |
| build_settings = {} | |
| # Extract target and build settings information from the plist dictionary | |
| for key, value in plist.items(): | |
| if isinstance(value, dict) and value.get('isa') == 'XCBuildConfiguration': | |
| build_settings.update(value.get('buildSettings', {})) | |
| for key, value in plist.items(): | |
| if isinstance(value, dict) and value.get('isa') == 'PBXNativeTarget': | |
| targets.append(value.get('name', '')) | |
| for key, value in plist.items(): | |
| if isinstance(value, dict) and value.get('isa') == 'PBXFileReference': | |
| file_path = value.get('path') | |
| if file_path and file_path.endswith(('.m', '.swift', '.c', '.cpp')): | |
| source_files.append(file_path) | |
| if file_path and file_path.endswith('.framework'): | |
| frameworks.append(file_path) | |
| return targets, source_files, frameworks, build_settings | |
| def find_all_source_files(root_dir): | |
| """ | |
| Recursively find all source files within the specified directory. | |
| """ | |
| return glob.glob(f"{root_dir}/**/*.[mc]", recursive=True) + \ | |
| glob.glob(f"{root_dir}/**/*.swift", recursive=True) + \ | |
| glob.glob(f"{root_dir}/**/*.cpp", recursive=True) | |
| def analyze_source_files(source_files): | |
| """ | |
| Analyze source files to determine dependencies. | |
| """ | |
| dependencies = set() | |
| for source_file in source_files: | |
| try: | |
| with open(source_file, 'r') as file: | |
| content = file.read() | |
| includes = re.findall(r'#include [<"](.*?)[>"]', content) | |
| imports = re.findall(r'@import (.*?);', content) | |
| dependencies.update(includes + imports) | |
| except Exception as e: | |
| print(f"Error reading source file {source_file}: {e}") | |
| return list(dependencies) | |
| def find_resources(root_dir): | |
| """ | |
| Find resource files such as images, storyboards, and other assets. | |
| """ | |
| resource_extensions = ['.png', '.jpg', '.jpeg', '.storyboard', '.xib', '.xcassets', '.json', '.plist'] | |
| resources = [] | |
| for ext in resource_extensions: | |
| resources.extend(glob.glob(f"{root_dir}/**/*{ext}", recursive=True)) | |
| return resources | |
| def find_submodules(root_dir): | |
| """ | |
| Find git submodules defined in the .gitmodules file. | |
| """ | |
| gitmodules_path = os.path.join(root_dir, '.gitmodules') | |
| submodules = [] | |
| if os.path.exists(gitmodules_path): | |
| with open(gitmodules_path, 'r') as f: | |
| content = f.read() | |
| submodules = re.findall(r'path = (.+)', content) | |
| return submodules | |
| def find_script_phases(pbxproj_path): | |
| """ | |
| Extract shell script build phases from the Xcode project file. | |
| """ | |
| try: | |
| with open(pbxproj_path, 'r') as file: | |
| content = file.read() | |
| plist = plistlib.loads(bytes(content, 'UTF-8')) | |
| except Exception as e: | |
| print(f"Error reading pbxproj file: {e}") | |
| return [] | |
| script_phases = [] | |
| for key, value in plist.items(): | |
| if isinstance(value, dict) and value.get('isa') == 'PBXShellScriptBuildPhase': | |
| script_phases.append(value.get('shellScript', '')) | |
| return script_phases | |
| def generate_package_swift(targets, source_files, frameworks, dependencies, resources, submodules, build_settings, script_phases): | |
| """ | |
| Generate the Package.swift file content based on the extracted project data. | |
| """ | |
| source_files_str = ', '.join(f'"{file}"' for file in source_files) | |
| frameworks_str = ', '.join(f'.linkedFramework("{framework}")' for framework in frameworks) | |
| dependencies_str = ', '.join(f'"{dep}"' for dep in dependencies) | |
| resources_str = ', '.join(f'"{res}"' for res in resources) | |
| submodules_str = ', '.join(f'"{sub}"' for sub in submodules) | |
| deployment_target = build_settings.get('IPHONEOS_DEPLOYMENT_TARGET', '13.0') | |
| c_flags = build_settings.get('OTHER_CFLAGS', '').split() | |
| cxx_flags = build_settings.get('OTHER_CPLUSPLUSFLAGS', '').split() | |
| c_settings = [f'.define("{flag}")' for flag in c_flags] | |
| cxx_settings = [f'.define("{flag}")' for flag in cxx_flags] | |
| header_search_paths = build_settings.get('HEADER_SEARCH_PATHS', '').split() | |
| for path in header_search_paths: | |
| c_settings.append(f'.headerSearchPath("{path}")') | |
| cxx_settings.append(f'.headerSearchPath("{path}")') | |
| package_swift = f""" | |
| // swift-tools-version:5.3 | |
| import PackageDescription | |
| let package = Package( | |
| name: "PPSSPPIOS", | |
| platforms: [ | |
| .iOS(.v{deployment_target}) | |
| ], | |
| products: [ | |
| .library( | |
| name: "PPSSPPIOS", | |
| targets: ["PPSSPPIOS"]), | |
| ], | |
| dependencies: [ | |
| // Add external dependencies here | |
| {submodules_str} | |
| ], | |
| targets: [ | |
| .target( | |
| name: "PPSSPPIOS", | |
| dependencies: [], | |
| path: "ios", | |
| exclude: ["README.md"], | |
| sources: [{source_files_str}], | |
| resources: [{resources_str}], | |
| publicHeadersPath: "include", | |
| cSettings: [ | |
| {', '.join(c_settings)} | |
| ], | |
| cxxSettings: [ | |
| {', '.join(cxx_settings)} | |
| ], | |
| linkerSettings: [ | |
| {frameworks_str} | |
| ] | |
| ) | |
| ], | |
| cLanguageStandard: .gnu11, | |
| cxxLanguageStandard: .gnucxx14 | |
| ) | |
| """ | |
| return package_swift | |
| def main(): | |
| project_path = "path/to/your/PPSSPP.xcodeproj/project.pbxproj" | |
| ios_dir = "path/to/your/ios/directory" | |
| root_dir = "path/to/your/project/root" | |
| targets, project_source_files, frameworks, build_settings = parse_pbxproj(project_path) | |
| all_source_files = find_all_source_files(ios_dir) | |
| dependencies = analyze_source_files(all_source_files) | |
| resources = find_resources(ios_dir) | |
| submodules = find_submodules(root_dir) | |
| script_phases = find_script_phases(project_path) | |
| # Combine and deduplicate source files | |
| source_files = list(set(project_source_files + all_source_files)) | |
| package_swift = generate_package_swift(targets, source_files, frameworks, dependencies, resources, submodules, build_settings, script_phases) | |
| with open("Package.swift", "w") as f: | |
| f.write(package_swift) | |
| print("Package.swift has been generated.") | |
| # Generate additional configuration files if needed | |
| if script_phases: | |
| with open("build_phases.json", "w") as f: | |
| json.dump(script_phases, f, indent=2) | |
| print("build_phases.json has been generated for custom build phases.") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment