Swiftgram/build-system/generate_spm.py
2025-05-05 17:58:19 +02:00

194 lines
8.8 KiB
Python

#! /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))