"""Bazel rules for generating SWIG language bindings. This module provides Bazel rules for generating language bindings using SWIG. Example usage: load("@swig//:swig.bzl", "swig_library") swig_library( name = "my_python_wrapper", srcs = [ "my_interface.i", "additional.i", "header.h", ], main = "my_interface.i", language = "python", module = "my_module", ) """ def _swig_library_impl(ctx): """Implementation function for swig_library rule. Args: ctx: Rule context Returns: DefaultInfo provider with generated wrapper and module files """ # Map language to the corresponding SWIG library attribute and extension # Add more languages as lib_ filegroups are added to @swig//:BUILD _LANGUAGE_TO_LIB_ATTR = { "python": ctx.files._swig_lib_python, "java": ctx.files._swig_lib_java, "tcl": ctx.files._swig_lib_tcl, } _LANGUAGE_MODULE_EXT = { "python": ".py", "java": ".java", # there is no *.tcl file needed as the interpreter can directly read } if (ctx.attr.language not in _LANGUAGE_TO_LIB_ATTR): fail("Language '%s' is not supported. Supported languages: %s. " % (ctx.attr.language, ", ".join(sorted(_LANGUAGE_TO_LIB_ATTR.keys()))) + "To add support for a new language, ensure @swig//:BUILD has a " + "'lib_%s' filegroup and add the mapping in swig.bzl." % ctx.attr.language) swig_lib_files = _LANGUAGE_TO_LIB_ATTR[ctx.attr.language] swig_lib_dir = ctx.file._swig_swg.dirname interface_file = None if ctx.attr.main: for src in ctx.files.srcs: if src.basename == ctx.attr.main or src.path.endswith("/" + ctx.attr.main): interface_file = src break if not interface_file: fail("Main interface file '%s' not found in srcs" % ctx.attr.main) else: # Default: use the first .i file in srcs for src in ctx.files.srcs: if src.extension == "i": interface_file = src break if not interface_file: fail("No .i interface file found in srcs") module = ctx.attr.module if ctx.attr.module else ctx.label.name if ctx.attr.out: wrapper_out = ctx.actions.declare_file(ctx.attr.out) else: # Default: {rule_name}_wrap.{c|cpp} wrapper_ext = ".c" if ctx.attr.out_c_mode else ".cpp" wrapper_out = ctx.actions.declare_file(ctx.label.name + "_wrap" + wrapper_ext) outputs = [wrapper_out] # Not all languages actually output a language-specific file if ctx.attr.language in _LANGUAGE_MODULE_EXT: module_ext = _LANGUAGE_MODULE_EXT[ctx.attr.language] if ctx.attr.outdir: module_out = ctx.actions.declare_file(ctx.attr.outdir + "/" + module + module_ext) else: # Default: same directory as the rule module_out = ctx.actions.declare_file(module + module_ext) outputs.append(module_out) include_dirs = {} for src in ctx.files.srcs: include_dirs[src.dirname] = True for include in ctx.attr.includes: include_dirs[include] = True include_dir_list = include_dirs.keys() swig_args = ctx.actions.args() swig_args.add("-" + ctx.attr.language) if not ctx.attr.out_c_mode: swig_args.add("-c++") swig_args.add_all(ctx.attr.defines, format_each = "-D%s") swig_args.add_all(include_dir_list, format_each = "-I%s") swig_args.add_all(ctx.attr.opts) swig_args.add("-module", module) if ctx.attr.namespace_prefix: swig_args.add("-namespace") swig_args.add("-prefix") swig_args.add(ctx.attr.namespace_prefix) swig_args.add("-outdir", ctx.attr.outdir) swig_args.add("-o", wrapper_out) swig_args.add(interface_file) ctx.actions.run( executable = ctx.executable._swig, arguments = [swig_args], inputs = depset(swig_lib_files + ctx.files.srcs), outputs = outputs, env = {"SWIG_LIB": swig_lib_dir}, mnemonic = "SwigGenerate", progress_message = "Generating SWIG %s bindings for %s" % (ctx.attr.language, ctx.label.name), ) if ctx.attr.runtime_header: runtime_header = ctx.actions.declare_file(ctx.attr.runtime_header) runtime_args = ctx.actions.args() runtime_args.add("-" + ctx.attr.language) runtime_args.add("-external-runtime") runtime_args.add(runtime_header) ctx.actions.run( outputs = [runtime_header], inputs = depset(swig_lib_files), env = {"SWIG_LIB": swig_lib_dir}, arguments = [runtime_args], executable = ctx.executable._swig, mnemonic = "GenerateSwigRuntimeHeader", toolchain = None, ) outputs.append(runtime_header) return [DefaultInfo(files = depset(outputs))] swig_library = rule( implementation = _swig_library_impl, attrs = { "srcs": attr.label_list( mandatory = True, allow_files = True, doc = "SWIG interface files (.i) and headers. All files are available for " + "%include directives. The main interface file is specified by 'main' " + "attribute, or defaults to the first .i file.", ), "main": attr.string( doc = "Main SWIG interface file (optional). If not specified, the first .i " + "file in srcs is used. Only the main file is passed to SWIG; other " + "files should be %include'd from the main file.", ), "language": attr.string( mandatory = True, doc = "Target language for binding generation.", ), "module": attr.string( doc = "SWIG module name. Defaults to the rule name if not specified.", ), "namespace_prefix": attr.string( mandatory = False, default = "", doc = "swig namespace prefix (for language=tcl)", ), "runtime_header": attr.string( mandatory = False, doc = "Create a header with the swig binding", ), "out": attr.string( doc = "Explicit name for the wrapper output file. If not specified, " + "defaults to '{rule_name}_wrap.{c|cpp}'.", ), "outdir": attr.string( doc = "Output directory for language-specific module files (e.g., .py, .java). " + "If specified, the module file will be placed in this subdirectory. " + "If not specified, module files are placed in the rule's directory.", ), "out_c_mode": attr.bool( default = False, doc = "If True, generate C wrapper instead of C++ (default: False).", ), "defines": attr.string_list( default = [], doc = "Preprocessor defines to pass to SWIG using -D flags.", ), "includes": attr.string_list( default = [], doc = "Additional include paths to pass to SWIG using -I flags. " + "Source file directories are automatically included.", ), "opts": attr.string_list( default = [], doc = "Additional SWIG command-line options.", ), "_swig": attr.label( default = "@swig//:swig", executable = True, cfg = "exec", doc = "The SWIG executable (internal attribute).", ), "_swig_swg": attr.label( default = "@swig//:swig_swg", allow_single_file = True, doc = "SWIG swig.swg library file used for determining SWIG_LIB " + "env variable (internal attribute).", ), "_swig_lib_python": attr.label( default = "@swig//:lib_python", allow_files = True, doc = "SWIG library files for Python (internal attribute).", ), "_swig_lib_java": attr.label( default = "@swig//:lib_java", allow_files = True, doc = "SWIG library files for Java (internal attribute).", ), "_swig_lib_tcl": attr.label( default = "@swig//:lib_tcl", allow_files = True, doc = "SWIG library files for Tcl (internal attribute).", ), }, doc = """Generate SWIG wrapper code for language bindings. This rule simplifies SWIG binding generation. The rule automatically selects the appropriate SWIG library files based on the 'language' attribute. To add support for additional languages, ensure the @swig repository has a corresponding 'lib_' filegroup in its BUILD file, then add the language mapping in the _LANGUAGE_TO_LIB_ATTR dictionary. Attributes: srcs: SWIG interface files (.i) and headers language: Target language (python, java, tcl, etc.) module: Module name (optional, defaults to rule name) out: Wrapper output filename (optional, defaults to {name}_wrap.{c|cpp}) outdir: Module file output directory (optional, defaults to rule directory) out_c_mode: Generate C wrapper instead of C++ (optional, default: False) defines: Preprocessor defines (optional) includes: Additional include paths (optional) opts: Additional SWIG options (optional) Example (Python - basic): swig_library( name = "my_python_wrapper", srcs = ["interface.i", "header.h"], language = "python", module = "my_module", defines = ["SWIGPYTHON"], ) # Generates: my_python_wrapper_wrap.cpp, my_module.py Example (Python - custom output): swig_library( name = "nlopt_swig", srcs = ["nlopt.i", "nlopt.h"], language = "python", module = "nlopt", out = "nlopt_wrap.cpp", outdir = "generated", defines = ["SWIGPYTHON"], ) # Generates: nlopt_wrap.cpp, generated/nlopt.py Example (Java): swig_library( name = "my_java_wrapper", srcs = ["interface.i", "header.h"], language = "java", module = "MyModule", defines = ["SWIGJAVA"], ) # Generates: my_java_wrapper_wrap.cpp, MyModule.java """, )