#! /usr/bin/env python3 import sys import os import sys import json import shutil # Read the modules JSON file modules_json_path = "bazel-bin/Telegram/spm_build_root_modules.json" with open(modules_json_path, 'r') as f: modules = json.load(f) # Clean spm-files spm_files_dir = "spm-files" if os.path.exists(spm_files_dir): shutil.rmtree(spm_files_dir) if not os.path.exists(spm_files_dir): os.makedirs(spm_files_dir) combined_lines = [] combined_lines.append("// swift-tools-version: 6.0") combined_lines.append("// The swift-tools-version declares the minimum version of Swift required to build this package.") combined_lines.append("") combined_lines.append("import PackageDescription") combined_lines.append("") combined_lines.append("let package = Package(") combined_lines.append(" name: \"Telegram\",") combined_lines.append(" platforms: [") combined_lines.append(" .iOS(.v13)") combined_lines.append(" ],") combined_lines.append(" products: [") for name, module in sorted(modules.items()): if module["type"] == "objc_library" or module["type"] == "swift_library" or module["type"] == "cc_library": combined_lines.append(" .library(name: \"%s\", targets: [\"%s\"])," % (module["name"], module["name"])) combined_lines.append(" ],") combined_lines.append(" targets: [") for name, module in sorted(modules.items()): module_type = module["type"] if module_type == "objc_library" or module_type == "cc_library" or module_type == "swift_library": combined_lines.append(" .target(") combined_lines.append(" name: \"%s\"," % name) linked_directory = None has_non_linked_sources = False for source in module["sources"]: if source.startswith("bazel-out/"): linked_directory = "spm-files/" + name else: has_non_linked_sources = True if linked_directory and has_non_linked_sources: print("Module {} has both regular and generated sources".format(name)) sys.exit(1) if linked_directory: os.makedirs(linked_directory) combined_lines.append(" dependencies: [") for dep in module["deps"]: combined_lines.append(" .target(name: \"%s\")," % dep) combined_lines.append(" ],") if linked_directory: combined_lines.append(" path: \"%s\"," % linked_directory) else: combined_lines.append(" path: \"%s\"," % module["path"]) combined_lines.append(" exclude: [") exclude_files_and_dirs = [] if not linked_directory: for root, dirs, files in os.walk(module["path"]): rel_path = os.path.relpath(root, module["path"]) if rel_path == ".": rel_path = "" else: rel_path += "/" # Add directories that should be excluded for d in dirs: dir_path = os.path.join(rel_path, d) if any(component.startswith('.') for component in dir_path.split('/')): continue # Check if any source file is under this directory has_source = False for source in module["sources"]: rel_source = source[len(module["path"]) + 1:] if rel_source.startswith(dir_path + "/"): has_source = True break if not has_source: exclude_files_and_dirs.append(dir_path) # Add files that should be excluded for f in files: file_path = os.path.join(rel_path, f) if any(component.startswith('.') for component in file_path.split('/')): continue if file_path not in [source[len(module["path"]) + 1:] for source in module["sources"]]: exclude_files_and_dirs.append(file_path) for item in exclude_files_and_dirs: combined_lines.append(" \"%s\"," % item) combined_lines.append(" ],") combined_lines.append(" sources: [") for source in module["sources"]: if source.endswith(('.h', '.hpp')): continue linked_source_file_names = [] if not source.startswith(module["path"]): if source.startswith("bazel-out/"): if not linked_directory: print("Source {} is a generated file, but module {} has no linked directory".format(source, name)) sys.exit(1) if module["path"] in source: source_file_name = source[source.index(module["path"]) + len(module["path"]) + 1:] else: print("Source {} is not inside module path {}".format(source, module["path"])) sys.exit(1) if source_file_name in linked_source_file_names: print("Source {} is a duplicate".format(source)) sys.exit(1) linked_source_file_names.append(source_file_name) # Create any parent directories needed for the source file symlink_location = os.path.join(linked_directory, source_file_name) source_dir = os.path.dirname(symlink_location) if not os.path.exists(source_dir): os.makedirs(source_dir) # Calculate the relative path from the symlink location back to the workspace root num_parent_dirs = 2 + source_file_name.count(os.path.sep) relative_prefix = "".join(["../"] * num_parent_dirs) symlink_target = relative_prefix + source os.symlink(symlink_target, symlink_location) relative_source = source_file_name combined_lines.append(" \"%s\"," % relative_source) else: print("Source {} is not inside module path {}".format(source, module["path"])) sys.exit(1) else: relative_source = source[len(module["path"]) + 1:] combined_lines.append(" \"%s\"," % relative_source) combined_lines.append(" ],") if module_type == "objc_library" or module_type == "cc_library": if len(module["includes"]) == 0: combined_lines.append(" publicHeadersPath: \"\",") elif len(module["includes"]) == 1: combined_lines.append(" publicHeadersPath: \"%s\"," % module["includes"][0]) else: print("Multiple includes are not supported yet: {}".format(module["includes"])) sys.exit(1) combined_lines.append(" cSettings: [") combined_lines.append(" .unsafeFlags([") for flag in module["copts"]: # Escape C-string entities in flag escaped_flag = flag.replace('\\', '\\\\').replace('"', '\\"') combined_lines.append(" \"%s\"," % escaped_flag) combined_lines.append(" ])") combined_lines.append(" ],") combined_lines.append(" linkerSettings: [") if module_type == "objc_library": for framework in module["sdk_frameworks"]: combined_lines.append(" .linkedFramework(\"%s\")," % framework) for dylib in module["sdk_dylibs"]: combined_lines.append(" .linkedLibrary(\"%s\")," % dylib) combined_lines.append(" ]") elif module_type == "swift_library": combined_lines.append(" swiftSettings: [") combined_lines.append(" .swiftLanguageMode(.v5),") combined_lines.append(" .unsafeFlags([") for flag in module["copts"]: combined_lines.append(" \"%s\"," % flag) combined_lines.append(" ])") combined_lines.append(" ]") combined_lines.append(" ),") elif module["type"] == "root": pass else: print("Unknown module type: {}".format(module["type"])) sys.exit(1) combined_lines.append(" ]") combined_lines.append(")") combined_lines.append("") with open("Package.swift", "w") as f: f.write("\n".join(combined_lines))