diff --git a/.cursorignore b/.cursorignore index 6f9f00ff49..db9dd609e9 100644 --- a/.cursorignore +++ b/.cursorignore @@ -1 +1,2 @@ # Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) +spm-files \ No newline at end of file diff --git a/.gitignore b/.gitignore index a1a1e19e20..4a8af5793f 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ buildServer.json .build/** Telegram.LSP.json **/.build/** +spm-files diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c0e2d31f2..2bb86d2b21 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,8 @@ }, "search.exclude": { ".git/**": true + }, + "files.associations": { + "memory": "cpp" } } diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index d1078efef7..67cbb89c3e 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -393,6 +393,58 @@ class BazelCommandLine: print(subprocess.list2cmdline(combined_arguments)) call_executable(combined_arguments) + def get_spm_aspect_invocation(self): + combined_arguments = [ + self.build_environment.bazel_path + ] + combined_arguments += self.get_startup_bazel_arguments() + combined_arguments += ['build'] + + if self.custom_target is not None: + combined_arguments += [self.custom_target] + else: + combined_arguments += ['Telegram/Telegram'] + + if self.continue_on_error: + combined_arguments += ['--keep_going'] + if self.show_actions: + combined_arguments += ['--subcommands'] + + if self.enable_sandbox: + combined_arguments += ['--spawn_strategy=sandboxed'] + + if self.disable_provisioning_profiles: + combined_arguments += ['--//Telegram:disableProvisioningProfiles'] + + if self.configuration_path is None: + raise Exception('configuration_path is not defined') + + combined_arguments += [ + '--override_repository=build_configuration={}'.format(self.configuration_path) + ] + + combined_arguments += self.common_args + combined_arguments += self.common_build_args + combined_arguments += self.get_define_arguments() + combined_arguments += self.get_additional_build_arguments() + + if self.remote_cache is not None: + combined_arguments += [ + '--remote_cache={}'.format(self.remote_cache), + '--experimental_remote_downloader={}'.format(self.remote_cache) + ] + elif self.cache_dir is not None: + combined_arguments += [ + '--disk_cache={path}'.format(path=self.cache_dir) + ] + + combined_arguments += self.configuration_args + + combined_arguments += ['--aspects', '//build-system/bazel-utils:spm.bzl%spm_text_aspect'] + + print(subprocess.list2cmdline(combined_arguments)) + call_executable(combined_arguments) + def clean(bazel, arguments): bazel_command_line = BazelCommandLine( @@ -696,6 +748,36 @@ def query(bazel, arguments): bazel_command_line.invoke_query(query_args) +def get_spm_aspect_invocation(bazel, arguments): + bazel_command_line = BazelCommandLine( + bazel=bazel, + override_bazel_version=arguments.overrideBazelVersion, + override_xcode_version=arguments.overrideXcodeVersion, + bazel_user_root=arguments.bazelUserRoot + ) + + if arguments.cacheDir is not None: + bazel_command_line.add_cache_dir(arguments.cacheDir) + elif arguments.cacheHost is not None: + bazel_command_line.add_remote_cache(arguments.cacheHost) + + resolve_configuration( + base_path=os.getcwd(), + bazel_command_line=bazel_command_line, + arguments=arguments, + additional_codesigning_output_path=None + ) + + bazel_command_line.set_configuration(arguments.configuration) + bazel_command_line.set_build_number(arguments.buildNumber) + bazel_command_line.set_custom_target(arguments.target) + bazel_command_line.set_continue_on_error(False) + bazel_command_line.set_show_actions(False) + bazel_command_line.set_enable_sandbox(False) + bazel_command_line.set_split_swiftmodules(False) + + bazel_command_line.get_spm_aspect_invocation() + def add_codesigning_common_arguments(current_parser: argparse.ArgumentParser): configuration_group = current_parser.add_mutually_exclusive_group(required=True) configuration_group.add_argument( @@ -1121,6 +1203,38 @@ if __name__ == '__main__': metavar='query_string' ) + spm_parser = subparsers.add_parser('spm', help='Generate SPM package') + spm_parser.add_argument( + '--target', + type=str, + help='A custom bazel target name to build.', + metavar='target_name' + ) + spm_parser.add_argument( + '--buildNumber', + required=False, + type=int, + default=10000, + help='Build number.', + metavar='number' + ) + spm_parser.add_argument( + '--configuration', + choices=[ + 'debug_universal', + 'debug_arm64', + 'debug_armv7', + 'debug_sim_arm64', + 'release_sim_arm64', + 'release_arm64', + 'release_armv7', + 'release_universal' + ], + required=True, + help='Build configuration' + ) + add_codesigning_common_arguments(spm_parser) + if len(sys.argv) < 2: parser.print_help() sys.exit(1) @@ -1229,6 +1343,8 @@ if __name__ == '__main__': test(bazel=bazel_path, arguments=args) elif args.commandName == 'query': query(bazel=bazel_path, arguments=args) + elif args.commandName == 'spm': + get_spm_aspect_invocation(bazel=bazel_path, arguments=args) else: raise Exception('Unknown command') except KeyboardInterrupt: diff --git a/build-system/bazel-rules/rules_xcodeproj b/build-system/bazel-rules/rules_xcodeproj index 44b6f046d9..41929acc4c 160000 --- a/build-system/bazel-rules/rules_xcodeproj +++ b/build-system/bazel-rules/rules_xcodeproj @@ -1 +1 @@ -Subproject commit 44b6f046d95b84933c1149fbf7f9d81fd4e32020 +Subproject commit 41929acc4c7c1da973c77871d0375207b9d0806f diff --git a/build-system/bazel-utils/spm.bzl b/build-system/bazel-utils/spm.bzl new file mode 100644 index 0000000000..793d804c2a --- /dev/null +++ b/build-system/bazel-utils/spm.bzl @@ -0,0 +1,447 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "SwiftInfo") +load("@bazel_skylib//lib:paths.bzl", "paths") +load("@bazel_skylib//lib:dicts.bzl", "dicts") + +# Define provider to propagate data +SPMModulesInfo = provider( + fields = { + "modules": "Dictionary of module information", + "transitive_sources": "Depset of all transitive source files", + } +) + +_IGNORE_CC_LIBRARY_ATTRS = [ + "data", + "applicable_licenses", + "alwayslink", + "aspect_hints", + "compatible_with", + "deprecation", + "exec_compatible_with", + "exec_properties", + "expect_failure", + "features", + "generator_function", + "generator_location", + "generator_name", + "generator_platform", + "generator_script", + "generator_tool", + "generator_toolchain", + "generator_toolchain_type", + "licenses", + "linkstamp", + "linkstatic", + "name", + "restricted_to", + "tags", + "target_compatible_with", + "testonly", + "to_json", + "to_proto", + "toolchains", + "transitive_configs", + "visibility", + "win_def_file", + "linkopts", +] + +_IGNORE_CC_LIBRARY_EMPTY_ATTRS = [ + "additional_compiler_inputs", + "additional_linker_inputs", + "hdrs_check", + "implementation_deps", + "include_prefix", + "strip_include_prefix", + "local_defines", +] + +_CC_LIBRARY_ATTRS = { + "copts": [], + "defines": [], + "deps": [], + "hdrs": [], + "includes": [], + "srcs": [], + "textual_hdrs": [], +} + +_CC_LIBRARY_REQUIRED_ATTRS = { +} + +_IGNORE_OBJC_LIBRARY_ATTRS = [ + "data", + "alwayslink", + "applicable_licenses", + "aspect_hints", + "compatible_with", + "enable_modules", + "exec_compatible_with", + "exec_properties", + "expect_failure", + "features", + "generator_function", + "generator_location", + "generator_name", + "deprecation", + "module_name", + "name", + "stamp", + "tags", + "target_compatible_with", + "testonly", + "to_json", + "to_proto", + "toolchains", + "transitive_configs", + "visibility", +] + +_IGNORE_OBJC_LIBRARY_EMPTY_ATTRS = [ + "implementation_deps", + "linkopts", + "module_map", + "non_arc_srcs", + "pch", + "restricted_to", + "textual_hdrs", + "sdk_includes", +] + +_OBJC_LIBRARY_ATTRS = { + "copts": [], + "defines": [], + "deps": [], + "hdrs": [], + "srcs": [], + "sdk_dylibs": [], + "sdk_frameworks": [], + "weak_sdk_frameworks": [], + "includes": [], +} + +_OBJC_LIBRARY_REQUIRED_ATTRS = [ + "module_name", +] + +_IGNORE_SWIFT_LIBRARY_ATTRS = [ + "data", + "always_include_developer_search_paths", + "alwayslink", + "applicable_licenses", + "aspect_hints", + "compatible_with", + "deprecation", + "exec_compatible_with", + "exec_properties", + "expect_failure", + "features", + "generated_header_name", + "generates_header", + "generator_function", + "generator_location", + "generator_name", + "linkstatic", + "module_name", + "name", + "package_name", + "restricted_to", + "tags", + "target_compatible_with", + "testonly", + "to_json", + "to_proto", + "toolchains", + "transitive_configs", + "visibility", +] + +_IGNORE_SWIFT_LIBRARY_EMPTY_ATTRS = [ + "plugins", + "private_deps", + "swiftc_inputs", +] + +_SWIFT_LIBRARY_ATTRS = { + "copts": [], + "defines": [], + "deps": [], + "linkopts": [], + "srcs": [], +} + +_SWIFT_LIBRARY_REQUIRED_ATTRS = [ + "module_name", +] + +_LIBRARY_CONFIGS = { + "cc_library": { + "ignore_attrs": _IGNORE_CC_LIBRARY_ATTRS, + "ignore_empty_attrs": _IGNORE_CC_LIBRARY_EMPTY_ATTRS, + "handled_attrs": _CC_LIBRARY_ATTRS, + "required_attrs": _CC_LIBRARY_REQUIRED_ATTRS, + }, + "objc_library": { + "ignore_attrs": _IGNORE_OBJC_LIBRARY_ATTRS, + "ignore_empty_attrs": _IGNORE_OBJC_LIBRARY_EMPTY_ATTRS, + "handled_attrs": _OBJC_LIBRARY_ATTRS, + "required_attrs": _OBJC_LIBRARY_REQUIRED_ATTRS, + }, + "swift_library": { + "ignore_attrs": _IGNORE_SWIFT_LIBRARY_ATTRS, + "ignore_empty_attrs": _IGNORE_SWIFT_LIBRARY_EMPTY_ATTRS, + "handled_attrs": _SWIFT_LIBRARY_ATTRS, + "required_attrs": _SWIFT_LIBRARY_REQUIRED_ATTRS, + }, +} + +def get_rule_atts(rule): + if rule.kind in _LIBRARY_CONFIGS: + config = _LIBRARY_CONFIGS[rule.kind] + ignore_attrs = config["ignore_attrs"] + ignore_empty_attrs = config["ignore_empty_attrs"] + handled_attrs = config["handled_attrs"] + required_attrs = config["required_attrs"] + + for attr_name in dir(rule.attr): + if attr_name.startswith("_"): + continue + if attr_name in ignore_attrs: + continue + if attr_name in ignore_empty_attrs: + attr_value = getattr(rule.attr, attr_name) + if attr_value == [] or attr_value == None or attr_value == "": + continue + else: + fail("Attribute {} is not empty: {}".format(attr_name, attr_value)) + if attr_name in handled_attrs: + continue + fail("Unknown attribute: {}".format(attr_name)) + + result = dict() + result["type"] = rule.kind + for attr_name in handled_attrs: + if hasattr(rule.attr, attr_name): + result[attr_name] = getattr(rule.attr, attr_name) + else: + result[attr_name] = handled_attrs[attr_name] # Use default value + for attr_name in required_attrs: + if not hasattr(rule.attr, attr_name): + if rule.kind == "objc_library" and attr_name == "module_name": + result[attr_name] = getattr(rule.attr, "name") + else: + fail("Required attribute {} is missing".format(attr_name)) + else: + result[attr_name] = getattr(rule.attr, attr_name) + result["name"] = getattr(rule.attr, "name") + return result + elif rule.kind == "ios_application": + result = dict() + result["type"] = "ios_application" + return result + elif rule.kind == "generate_spm": + result = dict() + result["type"] = "root" + return result + elif rule.kind == "apple_static_xcframework_import": + result = dict() + result["type"] = "apple_static_xcframework_import" + return result + else: + fail("Unknown rule kind: {}".format(rule.kind)) + +def _collect_spm_modules_impl(target, ctx): + # Skip targets without DefaultInfo + if not DefaultInfo in target: + return [] + + # Get module name + module_name = ctx.label.name + if hasattr(ctx.rule.attr, "module_name"): + module_name = ctx.rule.attr.module_name or ctx.label.name + + # Collect all modules and transitive sources from dependencies first + all_modules = {} + dep_transitive_sources_list = [] + + if hasattr(ctx.rule.attr, "deps"): + for dep in ctx.rule.attr.deps: + if SPMModulesInfo in dep: + # Merge the modules dictionaries + for label, info in dep[SPMModulesInfo].modules.items(): + all_modules[label] = info + # Add transitive sources depset from dependency to the list + dep_transitive_sources_list.append(dep[SPMModulesInfo].transitive_sources) + + # Merge all transitive sources from dependencies + transitive_sources_from_deps = depset(transitive = dep_transitive_sources_list) + + # Keep this for debugging later + # if result_attrs["type"] == "swift_library": + # print("Processing rule {}".format(ctx.label.name)) + # print("ctx.rule.kind = {}".format(ctx.rule.kind)) + # for attr_name in dir(ctx.rule.attr): + # print(" attr1: {}".format(attr_name)) + + result_attrs = get_rule_atts(ctx.rule) + + sources = [] + current_target_src_files = [] + if "srcs" in result_attrs: + for src_target in result_attrs["srcs"]: + src_files = src_target.files.to_list() + for f in src_files: + if f.extension in ["swift", "cc", "cpp", "h", "m", "mm", "s", "S"]: + current_target_src_files.append(f) + for src_file in src_files: + sources.append(src_file.path) + current_target_sources = depset(current_target_src_files) + + headers = [] + current_target_hdr_files = [] + if "hdrs" in result_attrs: + for hdr_target in result_attrs["hdrs"]: + hdr_files = hdr_target.files.to_list() + for f in hdr_files: + current_target_hdr_files.append(f) + for hdr_file in hdr_files: + headers.append(hdr_file.path) + current_target_headers = depset(current_target_hdr_files) + + module_type = result_attrs["type"] + + if module_type == "root": + pass + elif module_type == "apple_static_xcframework_import": + pass + elif module_type == "objc_library" or module_type == "swift_library" or module_type == "cc_library": + # Collect dependency labels + dep_names = [] + if "deps" in result_attrs: + for dep in result_attrs["deps"]: + if hasattr(dep, "label"): + dep_label = str(dep.label) + dep_name = dep_label.split(":")[-1] + dep_names.append(dep_name) + else: + fail("Missing dependency label") + + if module_type == "objc_library" or module_type == "swift_library": + if result_attrs["module_name"] != result_attrs["name"]: + fail("Module name mismatch: {} != {}".format(result_attrs["module_name"], result_attrs["name"])) + + # Extract the path from the label + # Example: @//path/ModuleName:ModuleSubname -> path/ModuleName + if not str(ctx.label).startswith("@//"): + fail("Invalid label: {}".format(ctx.label)) + module_path = str(ctx.label).split(":")[0].split("@//")[1] + + if module_type == "objc_library": + module_info = { + "name": result_attrs["name"], + "type": module_type, + "path": module_path, + "defines": result_attrs["defines"], + "deps": dep_names, + "sources": sorted(sources + headers), + "module_name": module_name, + "copts": result_attrs["copts"], + "sdk_frameworks": result_attrs["sdk_frameworks"], + "sdk_dylibs": result_attrs["sdk_dylibs"], + "weak_sdk_frameworks": result_attrs["weak_sdk_frameworks"], + "includes": result_attrs["includes"], + } + elif module_type == "cc_library": + module_info = { + "name": result_attrs["name"], + "type": module_type, + "path": module_path, + "defines": result_attrs["defines"], + "deps": dep_names, + "sources": sorted(sources + headers), + "module_name": module_name, + "copts": result_attrs["copts"], + "includes": result_attrs["includes"], + } + elif module_type == "swift_library": + module_info = { + "name": result_attrs["name"], + "type": module_type, + "path": module_path, + "deps": dep_names, + "sources": sorted(sources), + "module_name": module_name, + "copts": result_attrs["copts"], + } + else: + fail("Unknown module type: {}".format(module_type)) + + if result_attrs["name"] in all_modules: + fail("Duplicate module name: {}".format(result_attrs["name"])) + all_modules[result_attrs["name"]] = module_info + elif result_attrs["type"] == "ios_application": + pass + else: + fail("Unknown rule type: {}".format(ctx.rule.kind)) + + # Add current target's sources and headers to the transitive set + final_transitive_sources = depset(transitive = [ + transitive_sources_from_deps, + current_target_sources, + current_target_headers, + ]) + + # Return both the SPM output files and the provider with modules data and sources + return [ + SPMModulesInfo( + modules = all_modules, + transitive_sources = final_transitive_sources, + ), + ] + +spm_modules_aspect = aspect( + implementation = _collect_spm_modules_impl, + attr_aspects = ["deps"], +) + +def _generate_spm_impl(ctx): + outputs = [] + dep_transitive_sources_list = [] + + if len(ctx.attr.deps) != 1: + fail("generate_spm must have exactly one dependency") + if SPMModulesInfo not in ctx.attr.deps[0]: + fail("generate_spm must have a dependency with SPMModulesInfo provider") + + spm_info = ctx.attr.deps[0][SPMModulesInfo] + modules = spm_info.modules + + # Declare and write the modules JSON file + modules_json_out = ctx.actions.declare_file("%s_modules.json" % ctx.label.name) + ctx.actions.write( + output = modules_json_out, + content = json.encode_indent(modules, indent = " "), # Use encode_indent for readability + ) + outputs.append(modules_json_out) + + for dep in ctx.attr.deps: + if SPMModulesInfo in dep: + # Add transitive sources depset from dependency + dep_transitive_sources_list.append(dep[SPMModulesInfo].transitive_sources) + + # Merge all transitive sources from dependencies + transitive_sources_from_deps = depset(transitive = dep_transitive_sources_list) + + # Return DefaultInfo containing only the output files in the 'files' field, + # but include the transitive sources in 'runfiles' to enforce the dependency. + return [DefaultInfo( + files = depset(outputs), + runfiles = ctx.runfiles(transitive_files = transitive_sources_from_deps), + )] + +generate_spm = rule( + implementation = _generate_spm_impl, + attrs = { + 'deps' : attr.label_list(aspects = [spm_modules_aspect]), + }, +) diff --git a/build-system/generate_spm.py b/build-system/generate_spm.py new file mode 100644 index 0000000000..fea5dbf31c --- /dev/null +++ b/build-system/generate_spm.py @@ -0,0 +1,193 @@ +#! /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))