Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 102 additions & 1 deletion BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
load("//bazel:tcl_encode_or.bzl", "tcl_encode")
load("//bazel:tcl_wrap_cc.bzl", "tcl_wrap_cc")
load("//bazel:python_wrap_cc.bzl", "python_wrap_cc")

package(
features = [
Expand Down Expand Up @@ -69,7 +70,9 @@ OPENROAD_LIBRARY_DEPS = [
"//src/exa",
"//src/exa:ui",
"//src/fin",
# "//src/fin:ui",
"//src/gpl",
# "//src/gpl:ui",
"//src/grt",
"//src/grt:ui",
"//src/ifp",
Expand All @@ -79,11 +82,15 @@ OPENROAD_LIBRARY_DEPS = [
"//src/odb",
"//src/odb:ui",
"//src/pad",
# "//src/pad:ui",
"//src/par",
"//src/par:ui",
"//src/pdn",
# "//src/pdn:ui",
"//src/ppl",
# "//src/ppl:ui",
"//src/psm",
# "//src/psm:ui",
"//src/rcx",
"//src/rcx:ui",
"//src/rmp",
Expand Down Expand Up @@ -262,6 +269,92 @@ tcl_wrap_cc(
],
)

# This target compiles the SWIG C++ wrapper into a shared library (.so)
# that Python can load dynamically.
cc_binary(
name = "_openroadpy.so",
srcs = [":openroad_swig-py"],
linkshared = True,
deps = [
":openroad_lib", # Depends on the core odb C++ library
":ord",
"//src/odb",
"//src/ifp",
"//src/utl",
"//src/gpl",
"//src/ant",
"//src/cts",
"//src/dpl",
"//src/drt",
"//src/exa",
"//src/fin",
"//src/grt",
"//src/par",
"//src/pdn",
"//src/ppl",
"//src/psm",
"//src/rcx",
"//src/stt",
"//src/gui",
"//src/tap",
"@boost.stacktrace",
"@openroad_rules_python//python/cc:current_py_cc_headers",
],
)

# This packages the SWIG-generated Python wrapper (odb.py) and the
# compiled C++ extension (_odb.so) together.
py_library(
name = "ord_py",
srcs = [":openroad_swig-py"], # Use the .py output from the swig-py rule
# The data attribute makes the .so file available at runtime.
data = [":_openroadpy.so"],
deps = [
"//src/utl:utl_py",
"//src/odb:odb_py",
],
# This allows imports relative to the workspace root.
imports = ["."],
visibility = ["//visibility:public",]
)

python_wrap_cc(
name = "openroad_swig-py",
srcs = [
"src/OpenRoad-py.i",
":error_swig-py",
"include/ord/Tech.h",
"include/ord/Design.h",
"include/ord/Timing.h",
],
module = "openroadpy",
root_swig_src = "src/OpenRoad-py.i",
swig_includes = [
"include",
"src",
],
deps = [
"//src/gpl:swig-py",
"//src/ifp:swig-py",
"//src/ant:swig-py",
"//src/cts:swig-py",
"//src/dpl:swig-py",
"//src/drt:swig-py",
"//src/exa:swig-py",
"//src/fin:swig-py",
"//src/grt:swig-py",
"//src/par:swig-py",
"//src/pdn:swig-py",
"//src/ppl:swig-py",
"//src/psm:swig-py",
"//src/rcx:swig-py",
"//src/stt:swig-py",
"//src/tap:swig-py",
"//src/odb:swig-py",
"//src/utl:swig-py",
]
)

filegroup(
name = "error_swig",
srcs = [
Expand All @@ -270,9 +363,17 @@ filegroup(
visibility = ["@//:__subpackages__"],
)

filegroup(
name = "error_swig-py",
srcs = [
"src/Exception-py.i",
],
visibility = ["@//:__subpackages__"],
)

filegroup(
name = "design_swig",
srcs = [
"src/Design.i",
],
)
)
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ python.toolchain(
ignore_root_user_error = True,
python_version = "3.13",
)
use_repo(python, "python_3_13")

pip = use_extension("@openroad_rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
Expand Down
6 changes: 3 additions & 3 deletions bazel/InitRunFiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ class BazelInitializer
}

// Set the TCL_LIBRARY environment variable
std::string path = runfiles->Rlocation("tk_tcl/library/");
if (!path.empty()) {
setenv("TCL_LIBRARY", path.c_str(), 0);
const std::string tcl_path = runfiles->Rlocation("tk_tcl/library/");
if (!tcl_path.empty()) {
setenv("TCL_LIBRARY", tcl_path.c_str(), 0);
} else {
std::cerr << "Error: Could not locate 'tk_tcl/library/' in runfiles."
<< std::endl;
Expand Down
128 changes: 128 additions & 0 deletions bazel/python_wrap_cc.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2025-2025, The OpenROAD Authors

"""A Python SWIG wrapping rule

These rules generate a C++ src file that is expected to be used as srcs in
cc_library or cc_binary rules. See below for expected usage.
cc_library(srcs=[":python_foo"])
python_wrap_cc(name = "python_foo", srcs=["exception.i"],...)
"""
PythonSwigInfo = provider(
"PythonSwigInfo for taking dependencies on other swig info rules",
fields = [
"transitive_srcs",
"includes",
"swig_options",
],
)

def _get_transitive_srcs(srcs, deps):
return depset(
srcs,
transitive = [dep[PythonSwigInfo].transitive_srcs for dep in deps],
)

def _get_transitive_includes(local_includes, deps):
return depset(
local_includes,
transitive = [dep[PythonSwigInfo].includes for dep in deps],
)

def _get_transitive_options(options, deps):
return depset(
options,
transitive = [dep[PythonSwigInfo].swig_options for dep in deps],
)

def _python_wrap_cc_impl(ctx):
"""Generates a single C++ file from the provided srcs in a DefaultInfo."""
if len(ctx.files.srcs) > 1 and not ctx.attr.root_swig_src:
fail("If multiple src files are provided, root_swig_src must be specified.")

root_file = ctx.file.root_swig_src or ctx.files.srcs[0]

cc_outfile_name = ctx.attr.out or (ctx.attr.name + ".cc")
cc_output_file = ctx.actions.declare_file(cc_outfile_name)
py_outfile_name = ctx.attr.module + ".py"
py_output_file = ctx.actions.declare_file(py_outfile_name)

include_root_directory = ""
if ctx.label.package:
include_root_directory = ctx.label.package + "/"

src_inputs = _get_transitive_srcs(ctx.files.srcs + ctx.files.root_swig_src, ctx.attr.deps)
includes_paths = _get_transitive_includes(
["{}{}".format(include_root_directory, include) for include in ctx.attr.swig_includes],
ctx.attr.deps,
)
swig_options = _get_transitive_options(ctx.attr.swig_options, ctx.attr.deps)

args = ctx.actions.args()
args.add("-DBAZEL=1")
args.add("-python")
args.add("-c++")
args.add("-flatstaticmethod")
args.add("-module")
args.add(ctx.attr.module)
args.add_all(swig_options.to_list())
args.add_all(includes_paths.to_list(), format_each = "-I%s")
args.add("-o")
args.add(cc_output_file.path)
args.add(root_file.path)

ctx.actions.run(
outputs = [cc_output_file, py_output_file],
inputs = src_inputs,
arguments = [args],
tools = ctx.files._swig,
executable = ([file for file in ctx.files._swig if file.basename == "swig"][0]),
)
return [
DefaultInfo(files = depset([cc_output_file, py_output_file])),
PythonSwigInfo(
transitive_srcs = src_inputs,
includes = includes_paths,
swig_options = swig_options,
),
]

python_wrap_cc = rule(
implementation = _python_wrap_cc_impl,
attrs = {
"deps": attr.label_list(
allow_empty = True,
doc = "python_wrap_cc dependencies",
providers = [PythonSwigInfo],
),
"module": attr.string(
mandatory = True,
doc = "swig module",
),
"out": attr.string(
doc = "The name of the C++ source file generated by these rules. If not set, defaults to '<name>.cc'.",
),
"root_swig_src": attr.label(
allow_single_file = [".swig", ".i"],
doc = """If more than one swig file is included in this rule.
The root file must be explicitly provided. This is the file which will be passed to
swig for generation.""",
),
"srcs": attr.label_list(
allow_empty = False,
allow_files = [".i", ".swig", ".h", ".hpp", ".hh"],
doc = "Swig files that generate C++ files",
),
"swig_includes": attr.string_list(
doc = "List of directories relative to the BUILD file to append as -I flags to SWIG",
),
"swig_options": attr.string_list(
doc = "args to pass directly to the swig binary",
),
"_swig": attr.label(
default = "@org_swig//:swig_stable",
allow_files = True,
cfg = "exec",
),
},
)
10 changes: 8 additions & 2 deletions bazel/tcl_encode_or.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ def _tcl_encode_or_impl(ctx):
args.add("--varname", ctx.attr.char_array_name)
args.add("--namespace", ctx.attr.namespace)

# Only keep .tcl and .py files.
allowed_extensions = (".tcl", ".py")
filtered_sources = [
f for f in ctx.files.srcs if f.basename.endswith(allowed_extensions)
]

ctx.actions.run(
outputs = [output_file],
inputs = ctx.files.srcs,
inputs = filtered_sources,
arguments = [args],
tools = [ctx.executable._encode_script],
executable = ctx.toolchains["@rules_python//python:toolchain_type"].py3_runtime.interpreter,
Expand All @@ -44,7 +50,7 @@ tcl_encode = rule(
),
"srcs": attr.label_list(
allow_empty = False,
allow_files = [".tcl"],
allow_files = [".tcl", ".py"],
doc = "Files to be wrapped.",
),
"_encode_script": attr.label(
Expand Down
Loading