Skip to content

Commit 4596638

Browse files
authored
Merge pull request #8336 from QuantamHD/python_siwg
python: Add pypi package infrastructure to bazel
2 parents 5271d70 + 07a3459 commit 4596638

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1163
-117
lines changed

BUILD.bazel

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
55
load("//bazel:tcl_encode_or.bzl", "tcl_encode")
66
load("//bazel:tcl_wrap_cc.bzl", "tcl_wrap_cc")
7+
load("//bazel:python_wrap_cc.bzl", "python_wrap_cc", "PYTHON_STABLE_API_DEFINE")
78

89
package(
910
features = [
@@ -262,6 +263,94 @@ tcl_wrap_cc(
262263
],
263264
)
264265

266+
267+
# This target compiles the SWIG C++ wrapper into a shared library (.so)
268+
# that Python can load dynamically.
269+
cc_binary(
270+
name = "_openroadpy.so",
271+
srcs = [":openroad_swig-py"],
272+
linkshared = True,
273+
deps = [
274+
":openroad_lib", # Depends on the core odb C++ library
275+
":ord",
276+
"//src/odb",
277+
"//src/ifp",
278+
"//src/utl",
279+
"//src/gpl",
280+
"//src/ant",
281+
"//src/cts",
282+
"//src/dpl",
283+
"//src/drt",
284+
"//src/exa",
285+
"//src/fin",
286+
"//src/grt",
287+
"//src/par",
288+
"//src/pdn",
289+
"//src/ppl",
290+
"//src/psm",
291+
"//src/rcx",
292+
"//src/stt",
293+
"//src/gui",
294+
"//src/tap",
295+
"@boost.stacktrace",
296+
"@openroad_rules_python//python/cc:current_py_cc_headers",
297+
],
298+
defines = [PYTHON_STABLE_API_DEFINE],
299+
)
300+
301+
# This packages the SWIG-generated Python wrapper (odb.py) and the
302+
# compiled C++ extension (_odb.so) together.
303+
py_library(
304+
name = "ord_py",
305+
srcs = [":openroad_swig-py"], # Use the .py output from the swig-py rule
306+
# The data attribute makes the .so file available at runtime.
307+
data = [":_openroadpy.so"],
308+
deps = [
309+
"//src/utl:utl_py",
310+
"//src/odb:odb_py",
311+
],
312+
# This allows imports relative to the workspace root.
313+
imports = ["."],
314+
visibility = ["//visibility:public",]
315+
)
316+
317+
python_wrap_cc(
318+
name = "openroad_swig-py",
319+
srcs = [
320+
"src/OpenRoad-py.i",
321+
":error_swig-py",
322+
"include/ord/Tech.h",
323+
"include/ord/Design.h",
324+
"include/ord/Timing.h",
325+
],
326+
module = "openroadpy",
327+
root_swig_src = "src/OpenRoad-py.i",
328+
swig_includes = [
329+
"include",
330+
"src",
331+
],
332+
deps = [
333+
"//src/gpl:swig-py",
334+
"//src/ifp:swig-py",
335+
"//src/ant:swig-py",
336+
"//src/cts:swig-py",
337+
"//src/dpl:swig-py",
338+
"//src/drt:swig-py",
339+
"//src/exa:swig-py",
340+
"//src/fin:swig-py",
341+
"//src/grt:swig-py",
342+
"//src/par:swig-py",
343+
"//src/pdn:swig-py",
344+
"//src/ppl:swig-py",
345+
"//src/psm:swig-py",
346+
"//src/rcx:swig-py",
347+
"//src/stt:swig-py",
348+
"//src/tap:swig-py",
349+
"//src/odb:swig-py",
350+
"//src/utl:swig-py",
351+
]
352+
)
353+
265354
filegroup(
266355
name = "error_swig",
267356
srcs = [
@@ -270,9 +359,17 @@ filegroup(
270359
visibility = ["@//:__subpackages__"],
271360
)
272361

362+
filegroup(
363+
name = "error_swig-py",
364+
srcs = [
365+
"src/Exception-py.i",
366+
],
367+
visibility = ["@//:__subpackages__"],
368+
)
369+
273370
filegroup(
274371
name = "design_swig",
275372
srcs = [
276373
"src/Design.i",
277374
],
278-
)
375+
)

MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ python.toolchain(
138138
ignore_root_user_error = True,
139139
python_version = "3.13",
140140
)
141+
use_repo(python, "python_3_13")
141142

142143
pip = use_extension("@openroad_rules_python//python/extensions:pip.bzl", "pip")
143144
pip.parse(

bazel/InitRunFiles.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ class BazelInitializer
5050
}
5151

5252
// Set the TCL_LIBRARY environment variable
53-
std::string path = runfiles->Rlocation("tk_tcl/library/");
54-
if (!path.empty()) {
55-
setenv("TCL_LIBRARY", path.c_str(), 0);
53+
const std::string tcl_path = runfiles->Rlocation("tk_tcl/library/");
54+
if (!tcl_path.empty()) {
55+
setenv("TCL_LIBRARY", tcl_path.c_str(), 0);
5656
} else {
5757
std::cerr << "Error: Could not locate 'tk_tcl/library/' in runfiles."
5858
<< std::endl;

bazel/python_wrap_cc.bzl

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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+
)

bazel/tcl_encode_or.bzl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,15 @@ def _tcl_encode_or_impl(ctx):
1919
args.add("--varname", ctx.attr.char_array_name)
2020
args.add("--namespace", ctx.attr.namespace)
2121

22+
# Only keep .tcl and .py files.
23+
allowed_extensions = (".tcl", ".py")
24+
filtered_sources = [
25+
f for f in ctx.files.srcs if f.basename.endswith(allowed_extensions)
26+
]
27+
2228
ctx.actions.run(
2329
outputs = [output_file],
24-
inputs = ctx.files.srcs,
30+
inputs = filtered_sources,
2531
arguments = [args],
2632
tools = [ctx.executable._encode_script],
2733
executable = ctx.toolchains["@rules_python//python:toolchain_type"].py3_runtime.interpreter,
@@ -44,7 +50,7 @@ tcl_encode = rule(
4450
),
4551
"srcs": attr.label_list(
4652
allow_empty = False,
47-
allow_files = [".tcl"],
53+
allow_files = [".tcl", ".py"],
4854
doc = "Files to be wrapped.",
4955
),
5056
"_encode_script": attr.label(

bazel/tcl_wrap_cc.bzl

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,19 @@ TclSwigInfo = provider(
1818
],
1919
)
2020

21-
def _get_transative_srcs(srcs, deps):
21+
def _get_transitive_srcs(srcs, deps):
2222
return depset(
2323
srcs,
2424
transitive = [dep[TclSwigInfo].transitive_srcs for dep in deps],
2525
)
2626

27-
def _get_transative_includes(local_includes, deps):
27+
def _get_transitive_includes(local_includes, deps):
2828
return depset(
2929
local_includes,
3030
transitive = [dep[TclSwigInfo].includes for dep in deps],
3131
)
3232

33-
def _get_transative_options(options, deps):
33+
def _get_transitive_options(options, deps):
3434
return depset(
3535
options,
3636
transitive = [dep[TclSwigInfo].swig_options for dep in deps],
@@ -40,28 +40,23 @@ def _tcl_wrap_cc_impl(ctx):
4040
"""Generates a single C++ file from the provided srcs in a DefaultInfo.
4141
"""
4242
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.")
43+
fail("If multiple src files are provided, root_swig_src must be specified.")
4444

45-
root_file = ctx.file.root_swig_src
46-
root_file = root_file if root_file != None else ctx.files.srcs[0]
45+
root_file = ctx.file.root_swig_src or ctx.files.srcs[0]
4746

48-
root_label = ctx.attr.root_swig_src
49-
root_label = root_label if root_label != None else ctx.attr.srcs[0]
50-
root_label = root_label.label
51-
52-
outfile_name = ctx.attr.out if ctx.attr.out else ctx.attr.name + ".cc"
47+
outfile_name = ctx.attr.out or (ctx.attr.name + ".cc")
5348
output_file = ctx.actions.declare_file(outfile_name)
5449

5550
include_root_directory = ""
5651
if ctx.label.package:
5752
include_root_directory = ctx.label.package + "/"
5853

59-
src_inputs = _get_transative_srcs(ctx.files.srcs + ctx.files.root_swig_src, ctx.attr.deps)
60-
includes_paths = _get_transative_includes(
54+
src_inputs = _get_transitive_srcs(ctx.files.srcs + ctx.files.root_swig_src, ctx.attr.deps)
55+
includes_paths = _get_transitive_includes(
6156
["{}{}".format(include_root_directory, include) for include in ctx.attr.swig_includes],
6257
ctx.attr.deps,
6358
)
64-
swig_options = _get_transative_options(ctx.attr.swig_options, ctx.attr.deps)
59+
swig_options = _get_transitive_options(ctx.attr.swig_options, ctx.attr.deps)
6560

6661
args = ctx.actions.args()
6762
args.add("-tcl8")
@@ -133,17 +128,17 @@ tcl_wrap_cc = rule(
133128
doc = "swig namespace prefix",
134129
),
135130
"out": attr.string(
136-
doc = "The name of the C++ source file generated by these rules.",
131+
doc = "The name of the C++ source file generated by these rules. If not set, defaults to '<name>.cc'.",
137132
),
138133
"root_swig_src": attr.label(
139134
allow_single_file = [".swig", ".i"],
140135
doc = """If more than one swig file is included in this rule.
141-
The root file must be explicitly provided. This is the which will be passed to
136+
The root file must be explicitly provided. This is the file which will be passed to
142137
swig for generation.""",
143138
),
144139
"srcs": attr.label_list(
145140
allow_empty = False,
146-
allow_files = [".i", ".swig", ".h", ".hpp"],
141+
allow_files = [".i", ".swig", ".h", ".hpp", ".hh"],
147142
doc = "Swig files that generate C++ files",
148143
),
149144
"swig_includes": attr.string_list(

0 commit comments

Comments
 (0)