|
| 1 | +# SPDX-License-Identifier: BSD-3-Clause |
| 2 | +# Copyright (c) 2025-2025, The OpenROAD Authors |
| 3 | + |
| 4 | +"""A Python SWIG wrapping rule |
| 5 | +
|
| 6 | +These rules generate a C++ src file that is expected to be used as srcs in |
| 7 | +cc_library or cc_binary rules. See below for expected usage. |
| 8 | +cc_library(srcs=[":python_foo"]) |
| 9 | +python_wrap_cc(name = "python_foo", srcs=["exception.i"],...) |
| 10 | +""" |
| 11 | +PythonSwigInfo = provider( |
| 12 | + "PythonSwigInfo for taking dependencies on other swig info rules", |
| 13 | + fields = [ |
| 14 | + "transitive_srcs", |
| 15 | + "includes", |
| 16 | + "swig_options", |
| 17 | + ], |
| 18 | +) |
| 19 | + |
| 20 | +PYTHON_STABLE_API_DEFINE = "Py_LIMITED_API=0x030A0000" |
| 21 | + |
| 22 | +def _get_transitive_srcs(srcs, deps): |
| 23 | + return depset( |
| 24 | + srcs, |
| 25 | + transitive = [dep[PythonSwigInfo].transitive_srcs for dep in deps], |
| 26 | + ) |
| 27 | + |
| 28 | +def _get_transitive_includes(local_includes, deps): |
| 29 | + return depset( |
| 30 | + local_includes, |
| 31 | + transitive = [dep[PythonSwigInfo].includes for dep in deps], |
| 32 | + ) |
| 33 | + |
| 34 | +def _get_transitive_options(options, deps): |
| 35 | + return depset( |
| 36 | + options, |
| 37 | + transitive = [dep[PythonSwigInfo].swig_options for dep in deps], |
| 38 | + ) |
| 39 | + |
| 40 | +def _python_wrap_cc_impl(ctx): |
| 41 | + """Generates a single C++ file from the provided srcs in a DefaultInfo.""" |
| 42 | + if len(ctx.files.srcs) > 1 and not ctx.attr.root_swig_src: |
| 43 | + fail("If multiple src files are provided, root_swig_src must be specified.") |
| 44 | + |
| 45 | + root_file = ctx.file.root_swig_src or ctx.files.srcs[0] |
| 46 | + |
| 47 | + cc_outfile_name = ctx.attr.out or (ctx.attr.name + ".cc") |
| 48 | + cc_output_file = ctx.actions.declare_file(cc_outfile_name) |
| 49 | + py_outfile_name = ctx.attr.module + ".py" |
| 50 | + py_output_file = ctx.actions.declare_file(py_outfile_name) |
| 51 | + |
| 52 | + include_root_directory = "" |
| 53 | + if ctx.label.package: |
| 54 | + include_root_directory = ctx.label.package + "/" |
| 55 | + |
| 56 | + src_inputs = _get_transitive_srcs(ctx.files.srcs + ctx.files.root_swig_src, ctx.attr.deps) |
| 57 | + includes_paths = _get_transitive_includes( |
| 58 | + ["{}{}".format(include_root_directory, include) for include in ctx.attr.swig_includes], |
| 59 | + ctx.attr.deps, |
| 60 | + ) |
| 61 | + swig_options = _get_transitive_options(ctx.attr.swig_options, ctx.attr.deps) |
| 62 | + |
| 63 | + args = ctx.actions.args() |
| 64 | + args.add("-DBAZEL=1") |
| 65 | + args.add("-python") |
| 66 | + args.add("-c++") |
| 67 | + args.add("-flatstaticmethod") |
| 68 | + args.add("-module") |
| 69 | + args.add(ctx.attr.module) |
| 70 | + args.add_all(swig_options.to_list()) |
| 71 | + args.add_all(includes_paths.to_list(), format_each = "-I%s") |
| 72 | + args.add("-o") |
| 73 | + args.add(cc_output_file.path) |
| 74 | + args.add(root_file.path) |
| 75 | + |
| 76 | + ctx.actions.run( |
| 77 | + outputs = [cc_output_file, py_output_file], |
| 78 | + inputs = src_inputs, |
| 79 | + arguments = [args], |
| 80 | + tools = ctx.files._swig, |
| 81 | + executable = ([file for file in ctx.files._swig if file.basename == "swig"][0]), |
| 82 | + ) |
| 83 | + return [ |
| 84 | + DefaultInfo(files = depset([cc_output_file, py_output_file])), |
| 85 | + PythonSwigInfo( |
| 86 | + transitive_srcs = src_inputs, |
| 87 | + includes = includes_paths, |
| 88 | + swig_options = swig_options, |
| 89 | + ), |
| 90 | + ] |
| 91 | + |
| 92 | +python_wrap_cc = rule( |
| 93 | + implementation = _python_wrap_cc_impl, |
| 94 | + attrs = { |
| 95 | + "deps": attr.label_list( |
| 96 | + allow_empty = True, |
| 97 | + doc = "python_wrap_cc dependencies", |
| 98 | + providers = [PythonSwigInfo], |
| 99 | + ), |
| 100 | + "module": attr.string( |
| 101 | + mandatory = True, |
| 102 | + doc = "swig module", |
| 103 | + ), |
| 104 | + "out": attr.string( |
| 105 | + doc = "The name of the C++ source file generated by these rules. If not set, defaults to '<name>.cc'.", |
| 106 | + ), |
| 107 | + "root_swig_src": attr.label( |
| 108 | + allow_single_file = [".swig", ".i"], |
| 109 | + doc = """If more than one swig file is included in this rule. |
| 110 | + The root file must be explicitly provided. This is the file which will be passed to |
| 111 | + swig for generation.""", |
| 112 | + ), |
| 113 | + "srcs": attr.label_list( |
| 114 | + allow_empty = False, |
| 115 | + allow_files = [".i", ".swig", ".h", ".hpp", ".hh"], |
| 116 | + doc = "Swig files that generate C++ files", |
| 117 | + ), |
| 118 | + "swig_includes": attr.string_list( |
| 119 | + doc = "List of directories relative to the BUILD file to append as -I flags to SWIG", |
| 120 | + ), |
| 121 | + "swig_options": attr.string_list( |
| 122 | + doc = "args to pass directly to the swig binary", |
| 123 | + ), |
| 124 | + "_swig": attr.label( |
| 125 | + default = "@org_swig//:swig_stable", |
| 126 | + allow_files = True, |
| 127 | + cfg = "exec", |
| 128 | + ), |
| 129 | + }, |
| 130 | +) |
0 commit comments