From 8ecdaca45c7319d169a6645a571a5e0ea53eb6d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 6 Jul 2026 08:39:37 +0200 Subject: [PATCH 01/14] =?UTF-8?q?bazel:=20add=20`bazelisk=20run=20//:cmake?= =?UTF-8?q?`=20=E2=80=94=20dependency=20prefix=20for=20plain=20CMake=20bui?= =?UTF-8?q?lds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Materialize every dependency of the CMake build into /deps from the pinned bazel module graph, as an alternative to etc/DependencyInstaller.sh: bazelisk run //:cmake cmake -DCMAKE_TOOLCHAIN_FILE=deps/toolchain.cmake -B build . cmake --build build -j$(nproc) deps/ contains the merged header tree, every static archive, the build tools (swig, bison, flex), the Tcl 9 script library, a hermetic CPython (headers, libpython, stdlib), CMake package config shims, and the hermetic clang/libc++ toolchain behind generated bin/cc, bin/c++ wrappers. Host requirements shrink to bazelisk, cmake, ninja or make, git and bash: no sudo, no compiler packages, nothing installed outside the workspace. Linux x86_64; the GUI is not part of the prefix (host Qt5 is a libstdc++ build and cannot link against libc++ archives). Compiler and linker flags are extracted from the resolved C++ toolchain at analysis time (cc_common.get_memory_inefficient_command_line), so the wrappers cannot drift from what bazel itself uses when the llvm module is bumped. Archives are linked through one interface target carrying a --start-group pool, making archive order irrelevant; the seven or-tools alwayslink archives are wrapped in --whole-archive. Dependency versions are parsed out of MODULE.bazel into a generated deps-versions.cmake consumed by the shims. The existing DependencyInstaller.sh/Build.sh flow is unaffected. The only change to existing CMake files is gating src/gui's find_package(OpenGL REQUIRED) behind Qt5_FOUND AND BUILD_GUI: OpenGL::GL is only referenced inside that branch, and requiring OpenGL on GUI-less hosts made every non-GUI build demand OpenGL development headers. Signed-off-by: Øyvind Harboe --- .gitignore | 3 + BUILD.bazel | 9 + README.md | 18 + cmake-deps/BUILD.bazel | 115 ++++ cmake-deps/assemble_bundle.py | 141 +++++ cmake-deps/defs.bzl | 619 ++++++++++++++++++++++ cmake-deps/materialize.sh | 70 +++ cmake-deps/shims/BoostConfig.cmake | 37 ++ cmake-deps/shims/BoostConfigVersion.cmake | 16 + cmake-deps/shims/Eigen3Config.cmake | 18 + cmake-deps/shims/GTestConfig.cmake | 29 + cmake-deps/shims/LEMONConfig.cmake | 13 + cmake-deps/shims/abslConfig.cmake | 19 + cmake-deps/shims/fmtConfig.cmake | 22 + cmake-deps/shims/ortoolsConfig.cmake | 18 + cmake-deps/shims/spdlogConfig.cmake | 20 + cmake-deps/shims/yaml-cppConfig.cmake | 20 + cmake-deps/toolchain.cmake.in | 92 ++++ docs/user/Build.md | 34 ++ src/gui/CMakeLists.txt | 7 +- 20 files changed, 1319 insertions(+), 1 deletion(-) create mode 100644 cmake-deps/BUILD.bazel create mode 100644 cmake-deps/assemble_bundle.py create mode 100644 cmake-deps/defs.bzl create mode 100755 cmake-deps/materialize.sh create mode 100644 cmake-deps/shims/BoostConfig.cmake create mode 100644 cmake-deps/shims/BoostConfigVersion.cmake create mode 100644 cmake-deps/shims/Eigen3Config.cmake create mode 100644 cmake-deps/shims/GTestConfig.cmake create mode 100644 cmake-deps/shims/LEMONConfig.cmake create mode 100644 cmake-deps/shims/abslConfig.cmake create mode 100644 cmake-deps/shims/fmtConfig.cmake create mode 100644 cmake-deps/shims/ortoolsConfig.cmake create mode 100644 cmake-deps/shims/spdlogConfig.cmake create mode 100644 cmake-deps/shims/yaml-cppConfig.cmake create mode 100644 cmake-deps/toolchain.cmake.in diff --git a/.gitignore b/.gitignore index 93a7b082f51..b845e8c8b1d 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,9 @@ user.bazelrc # Limit ignore to these files, so we see and clean out other cruft that pollutes grep /bazel-* +# CMake dependency prefix written by `bazelisk run //:cmake` +/deps/ + tmp/ projectview.bazelproject diff --git a/BUILD.bazel b/BUILD.bazel index 92d4eb3c19b..68afaaeeede 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -514,6 +514,15 @@ alias( visibility = ["//visibility:public"], ) +# `bazelisk run //:cmake` materializes /deps, a dependency +# prefix (headers, static libs, tools, hermetic clang toolchain) for +# building OpenROAD with plain CMake. See docs/user/Build.md. +alias( + name = "cmake", + actual = "//cmake-deps:cmake", + visibility = ["//visibility:public"], +) + # Lightweight test suites that run without building OpenROAD. # Usage: # bazelisk test --test_tag_filters=doc_check //src/... # all documentation checks diff --git a/README.md b/README.md index c77a65de821..c950f7c9c28 100644 --- a/README.md +++ b/README.md @@ -280,6 +280,24 @@ transceivers, OpenPower-based Microwatt etc. To build OpenROAD tools locally on your machine, follow steps from [here](docs/user/Build.md). +### CMake build with Bazel-provided dependencies + +On Linux x86_64, all CMake dependencies — compiler included — can be +materialized into a local `deps/` folder from the pinned Bazel module +graph, instead of running `etc/DependencyInstaller.sh`. The only host +requirements are `bazelisk`, `cmake`, `ninja` (or `make`), `git` and +`bash`; no `sudo`, and nothing is installed outside the workspace: + +``` shell +bazelisk run //:cmake +cmake -DCMAKE_TOOLCHAIN_FILE=deps/toolchain.cmake -B build . +cmake --build build -j$(nproc) +``` + +The developer workflow after that is plain CMake. See +[docs/user/Build.md](docs/user/Build.md#dependencies-from-bazel-no-dependencyinstallersh) +for details and runtime environment variables. + ### Third-party dependencies: submodules vs. Bazel BCR OpenROAD ships two build systems, and they source third-party code diff --git a/cmake-deps/BUILD.bazel b/cmake-deps/BUILD.bazel new file mode 100644 index 00000000000..c7f6f400c69 --- /dev/null +++ b/cmake-deps/BUILD.bazel @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors + +load("@rules_python//python:defs.bzl", "py_binary") +load("@rules_shell//shell:sh_binary.bzl", "sh_binary") +load(":defs.bzl", "cmake_deps_bundle") + +py_binary( + name = "assemble_bundle", + srcs = ["assemble_bundle.py"], + visibility = ["//visibility:private"], +) + +# The dependency prefix for plain-CMake builds of OpenROAD. The dep list +# is the union of everything OpenROAD's CMake files find_package() or +# find_library(): headers land in include/, static archives in lib/pool/. +# Header-only modules (most of boost, eigen, spdlog) contribute headers +# through their transitive compilation contexts. +cmake_deps_bundle( + name = "bundle", + deps = [ + "@abseil-cpp//absl/hash", + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/synchronization", + "@boost.algorithm", + "@boost.asio", + "@boost.beast", + "@boost.bind", + "@boost.config", + "@boost.container", + "@boost.container_hash", + "@boost.format", + "@boost.fusion", + "@boost.geometry", + "@boost.graph", + "@boost.heap", + "@boost.icl", + "@boost.integer", + "@boost.io", + "@boost.iostreams", + "@boost.iterator", + "@boost.json", + "@boost.lambda", + "@boost.lexical_cast", + "@boost.multi_array", + "@boost.optional", + "@boost.phoenix", + "@boost.polygon", + "@boost.property_tree", + "@boost.random", + "@boost.range", + "@boost.regex", + "@boost.serialization", + "@boost.smart_ptr", + "@boost.spirit", + "@boost.stacktrace", + "@boost.system", + "@boost.thread//:thread_posix", + "@boost.tokenizer", + "@boost.unordered", + "@boost.utility", + "@coin-or-lemon//:lemon", + "@cudd", + "@eigen", + "@fmt", + "@googletest//:gtest", + "@googletest//:gtest_main", + "@openmp", + "@or-tools//ortools/linear_solver", + "@or-tools//ortools/sat:cp_model", + "@or-tools//ortools/util:sorted_interval_list", + # Provides FlexLexer.h for flex C++ scanners (sta). + "@rules_flex//flex:current_flex_toolchain", + "@spdlog", + "@tcl_lang//:tcl", + "@yaml-cpp", + "@zlib", + ], + exclude_from_pool = ["googletest"], + include_overrides = { + # cudd's util.h etc. would collide in a flat include/; FindCUDD + # searches the include/cudd suffix. + "cudd": "cudd", + }, + lib_name_overrides = { + # FindZLIB and FindCUDD search by library name under _ROOT; + # the clang driver resolves -lomp during FindOpenMP's checks. + "cudd": "cudd", + "openmp": "omp", + "zlib": "z", + }, + python_headers = "@rules_python//python/cc:current_py_cc_headers", + python_libs = "@rules_python//python/cc:current_py_cc_libs", + shims = glob(["shims/*.cmake"]), + swig = "@swig", + swig_lib = [ + "@swig//:lib_python", + "@swig//:lib_tcl", + "@swig//:swig_swg", + ], + tcl_library = "@tcl_lang//:tcl_core", + toolchain_template = "toolchain.cmake.in", + versions_src = "//:MODULE.bazel", + visibility = ["//visibility:private"], +) + +# `bazelisk run //:cmake` (alias of this target) copies :bundle into +# /deps so OpenROAD can be built with plain CMake. +sh_binary( + name = "cmake", + srcs = ["materialize.sh"], + data = [":bundle"], + visibility = ["//visibility:public"], + deps = ["@bazel_tools//tools/bash/runfiles"], +) diff --git a/cmake-deps/assemble_bundle.py b/cmake-deps/assemble_bundle.py new file mode 100644 index 00000000000..455408e1c24 --- /dev/null +++ b/cmake-deps/assemble_bundle.py @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors + +"""Assemble the CMake dependency prefix from a manifest. + +Executed as a bazel action by the cmake_deps_bundle rule in defs.bzl. +Copies files and directories into the output tree, deduplicating large +files that resolve to the same inode by relative symlinks (the hermetic +LLVM distribution multiplexes every tool into one ~190 MB binary reached +through symlinks; copying it once per tool name would multiply the bundle +size), writes the generated wrapper scripts and CMake include files, and +renders toolchain.cmake from its template. +""" + +import filecmp +import json +import os +import re +import shutil +import stat +import sys + +# Files at least this large are deduplicated via relative symlinks when +# several destinations resolve to the same underlying file. +DEDUP_THRESHOLD_BYTES = 1024 * 1024 + + +class Assembler: + def __init__(self, out_root): + self.out_root = out_root + # (st_dev, st_ino) -> first destination path, for large files. + self.seen_inodes = {} + + def copy_file(self, src, dest): + os.makedirs(os.path.dirname(dest), exist_ok=True) + if os.path.lexists(dest): + if filecmp.cmp(src, dest, shallow=False): + return + raise SystemExit( + f"error: destination collision with differing contents: " + f"{dest} (from {src})" + ) + st = os.stat(src) + key = (st.st_dev, st.st_ino) + if st.st_size >= DEDUP_THRESHOLD_BYTES: + first = self.seen_inodes.get(key) + if first is not None: + target = os.path.relpath(first, os.path.dirname(dest)) + os.symlink(target, dest) + return + self.seen_inodes[key] = dest + shutil.copyfile(src, dest) + os.chmod(dest, st.st_mode & 0o777 | stat.S_IWUSR) + + def copy_tree(self, src, dest): + for root, _, files in os.walk(src, followlinks=True): + rel = os.path.relpath(root, src) + for name in files: + self.copy_file( + os.path.join(root, name), + os.path.normpath(os.path.join(dest, rel, name)), + ) + + def copy(self, src, dest): + if os.path.isdir(src): + self.copy_tree(src, dest) + else: + self.copy_file(src, dest) + + def write(self, dest, content, executable): + os.makedirs(os.path.dirname(dest), exist_ok=True) + with open(dest, "w") as f: + f.write(content) + os.chmod(dest, 0o755 if executable else 0o644) + + +def parse_versions(module_bazel_path): + """Dependency versions from MODULE.bazel, .bcr suffixes stripped.""" + text = open(module_bazel_path).read() + versions = {} + variables = dict(re.findall(r'^(\w+) = "([^"]+)"', text, re.MULTILINE)) + for name, version in re.findall( + r'bazel_dep\(\s*name = "([^"]+)",\s*version = ("[^"]+"|\w+)', text + ): + if version.startswith('"'): + version = version.strip('"') + else: + version = variables.get(version, "") + versions[name] = version.split(".bcr.")[0] + for name, version in list(versions.items()): + if name.startswith("boost."): + versions.setdefault("boost", version) + return versions + + +def versions_cmake(versions): + lines = ["# Generated by //cmake-deps from MODULE.bazel."] + for name, version in sorted(versions.items()): + variable = re.sub(r"[^A-Za-z0-9]", "_", name) + lines.append(f'set(OPENROAD_DEPS_VERSION_{variable} "{version}")') + return "\n".join(lines) + "\n" + + +def main(): + manifest_path, out_root = sys.argv[1], sys.argv[2] + with open(manifest_path) as f: + manifest = json.load(f) + + assembler = Assembler(out_root) + for entry in manifest["copies"]: + assembler.copy(entry["src"], os.path.join(out_root, entry["dest"])) + + for entry in manifest["writes"]: + assembler.write( + os.path.join(out_root, entry["dest"]), + entry["content"], + entry["executable"], + ) + + versions = parse_versions(manifest["module_bazel"]) + assembler.write( + os.path.join(out_root, manifest["versions_dest"]), + versions_cmake(versions), + executable=False, + ) + + template = open(manifest["template_src"]).read() + for key, value in manifest["substitutions"].items(): + template = template.replace(key, value) + unresolved = re.findall(r"@[A-Z_]+@", template) + if unresolved: + raise SystemExit(f"error: unresolved template keys: {unresolved}") + assembler.write( + os.path.join(out_root, manifest["template_dest"]), + template, + executable=False, + ) + + +if __name__ == "__main__": + main() diff --git a/cmake-deps/defs.bzl b/cmake-deps/defs.bzl new file mode 100644 index 00000000000..15f7be09a67 --- /dev/null +++ b/cmake-deps/defs.bzl @@ -0,0 +1,619 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors + +"""Materialize OpenROAD's third-party dependencies as a CMake prefix. + +`bazelisk run //:cmake` copies the bundle built here into `/deps` +so OpenROAD can be built with plain CMake against bazel-built dependencies +and the hermetic clang toolchain — no DependencyInstaller.sh, no sudo, no +host compilers. See docs/user/Build.md. + +The bundle is assembled by a single cached action: + include/ merged header tree of every dependency CMake looks up + lib/pool/ every static archive at its repo-relative path + lib/ conventionally named copies for find_library-based modules + lib/cmake/ package config shims + generated pool/versions include files + lib/tcl9.0/ the Tcl script library (init.tcl etc.) + llvm/ the hermetic clang toolchain, staged exactly as bazel does + bin/ cc/c++/swig/bison/flex wrapper scripts + tools/ swig/bison/flex binaries and support trees + python/ hermetic CPython (interpreter, headers, libpython, stdlib) + +Compiler and linker flags are extracted from the resolved C++ toolchain at +analysis time (the rules_foreign_cc technique), so they cannot drift from +what bazel itself uses when the `llvm` module is bumped. +""" + +load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES") +load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain", "use_cc_toolchain") + +# Flags owned by the CMake build type, not by the toolchain. Everything else +# extracted from the bazel toolchain is kept verbatim. +_FLAG_DENYLIST_PREFIXES = [ + "-O", + "-g", + "-DNDEBUG", + "-D_FORTIFY_SOURCE", + "-std=", + "-frandom-seed", + "-ffile-compilation-dir", + "-fdebug-prefix-map", +] + +_FLAG_DENYLIST_EXACT = [ + "-c", + "-S", + "-E", +] + +def _keep_flag(flag): + if flag in _FLAG_DENYLIST_EXACT: + return False + for prefix in _FLAG_DENYLIST_PREFIXES: + if flag.startswith(prefix): + return False + + # Warning flags are per-project policy; -Wl,/-Wa,/-Wp, are not + # warnings, and -Wno- flags only silence noise the kept toolchain + # flags themselves cause (e.g. the __DATE__ redaction). + if flag.startswith("-W") and not ( + flag.startswith("-Wl,") or + flag.startswith("-Wa,") or + flag.startswith("-Wp,") or + flag.startswith("-Wno-") + ): + return False + return True + +def _map_exec_path(path): + """Map an exec path to its stable location inside the bundle. + + Source files keep their exec path; generated files drop the + configuration segment so the layout is configuration-independent: + external/X -> external/X + bazel-out//bin/X -> gen/X + """ + if path.startswith("bazel-out/"): + parts = path.split("/") + if len(parts) > 3 and parts[2] == "bin": + return "gen/" + "/".join(parts[3:]) + return "gen/" + "/".join(parts[2:]) + return path + +def _rewrite_flag(flag, subtree): + """Rewrite exec paths embedded in a toolchain flag to bundle paths. + + "$R" is a placeholder the wrapper script resolves to the prefix root. + """ + for marker in ["bazel-out/", "external/"]: + prefix, found, rest = flag.partition(marker) + + # Only rewrite when the marker starts a path: at the start of the + # flag, after '=' (--sysroot=external/...), or after a short + # option (-Lbazel-out/..., -Bbazel-out/...). + if found and ( + prefix == "" or + prefix.endswith("=") or + (prefix.startswith("-") and "/" not in prefix) + ): + return prefix + "$R/" + subtree + "/" + _map_exec_path(marker + rest) + return flag + +def _shell_quote(arg): + """Double-quote for bash, keeping ${R} expandable. + + Toolchain flags contain literal quotes (-D__DATE__="redacted") that + must survive into the macro value. + """ + return '"' + arg.replace("\\", "\\\\").replace('"', '\\"').replace("`", "\\`") + '"' + +def _module_name(workspace_name): + """Bazel module name of a canonical repository name. + + "googletest+" and "rules_python++python+python_3_13_x86_64-..." both + map to their leading module name. + """ + return workspace_name.partition("+")[0] + +def _toolchain_command_lines(ctx, cc_toolchain): + """Extract compile and link command lines from the resolved toolchain.""" + feature_configuration = cc_common.configure_features( + ctx = ctx, + cc_toolchain = cc_toolchain, + requested_features = ctx.features, + unsupported_features = ctx.disabled_features, + ) + compile_variables = cc_common.create_compile_variables( + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + user_compile_flags = [], + ) + link_variables = cc_common.create_link_variables( + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + is_static_linking_mode = True, + is_linking_dynamic_library = False, + user_link_flags = [], + ) + + def flags(action_name, variables): + return [ + f + for f in cc_common.get_memory_inefficient_command_line( + feature_configuration = feature_configuration, + action_name = action_name, + variables = variables, + ) + if _keep_flag(f) + ] + + def tool(action_name): + return cc_common.get_tool_for_action( + feature_configuration = feature_configuration, + action_name = action_name, + ) + + return struct( + feature_configuration = feature_configuration, + c_compiler = tool(ACTION_NAMES.c_compile), + cxx_compiler = tool(ACTION_NAMES.cpp_compile), + archiver = tool(ACTION_NAMES.cpp_link_static_library), + c_flags = flags(ACTION_NAMES.c_compile, compile_variables), + cxx_flags = flags(ACTION_NAMES.cpp_compile, compile_variables), + link_flags = flags(ACTION_NAMES.cpp_link_executable, link_variables), + ) + +def _repo_relative_path(file): + """Path of `file` inside its repository, without the repo prefix.""" + marker = "external/" + file.owner.workspace_name + "/" + _, _, rest = file.path.partition(marker) + return rest or file.path + +def _include_roots(compilation_context): + roots = [] + for root in ( + compilation_context.includes.to_list() + + compilation_context.system_includes.to_list() + + compilation_context.quote_includes.to_list() + + compilation_context.external_includes.to_list() + ): + # "." (the exec root) and bare bazel-out bin dirs would claim every + # header with a bogus destination; real include dirs win by length. + if root == "." or (root.startswith("bazel-out/") and root.endswith("/bin")): + continue + roots.append(root) + return roots + +def _header_dest(header, sorted_roots, include_overrides): + """Destination of a header inside include/, or None to skip it.""" + path = header.path + for root in sorted_roots: + if path.startswith(root + "/"): + rel = path[len(root) + 1:] + override = include_overrides.get(_module_name(header.owner.workspace_name)) + if override: + return "include/" + override + "/" + rel + return "include/" + rel + return None + +def _archive_dest(library_file): + """Destination of a static archive inside lib/pool/.""" + _, _, rest = library_file.path.partition("external/") + return "lib/pool/" + (rest or library_file.path) + +def _wrapper_script(compiler, flags, link_args, defines): + """A relocatable compiler driver wrapper. + + The toolchain flags always apply. Link inputs (the static C++ runtime + archives and the toolchain link flags) must come after user objects and + libraries, and only when the driver actually links. + """ + lines = [ + "#!/usr/bin/env bash", + "# Generated by //cmake-deps. Wraps the hermetic clang toolchain", + "# with the exact flags the bazel build uses.", + "set -u", + 'R="$(cd "$(dirname "$0")/.." && pwd)"', + "link=1", + 'for arg in "$@"; do', + ' case "$arg" in', + " -c|-S|-E|-M|-MM|--version|-v|-dumpversion|-dumpmachine|--help|-print-*)", + " link=0", + " ;;", + " esac", + "done", + "post=()", + 'if [[ "$link" -eq 1 && "$#" -gt 0 ]]; then', + " post=(" + " ".join( + ["-Qunused-arguments"] + [ + _shell_quote(arg.replace("$R", "${R}")) + for arg in link_args + ], + ) + ")", + "fi", + ] + args = [_shell_quote(f.replace("$R", "${R}")) for f in flags] + args += [_shell_quote("-D" + d) for d in defines] + lines.append( + 'exec "${R}/llvm/' + _map_exec_path(compiler) + '" \\\n ' + + " \\\n ".join(args) + + ' \\\n "$@" ${post[@]+"${post[@]}"}', + ) + return "\n".join(lines) + "\n" + +def _tool_wrapper_script(tool_path, env): + """A relocatable wrapper for a build tool (swig/bison/flex).""" + lines = [ + "#!/usr/bin/env bash", + "# Generated by //cmake-deps.", + "set -u", + 'R="$(cd "$(dirname "$0")/.." && pwd)"', + ] + for key, value in sorted(env.items()): + lines.append('export {}="{}"'.format(key, value.replace("$R", "${R}"))) + lines.append('exec "' + tool_path.replace("$R", "${R}") + '" "$@"') + return "\n".join(lines) + "\n" + +def _pool_cmake(pool_dests, alwayslink_dests): + """lib/cmake/openroad_deps/deps-pool.cmake content. + + One interface target carrying every archive in a link group; static + over-linking is free (the linker only extracts needed members) and + makes archive order irrelevant. + """ + lines = [ + "# Generated by //cmake-deps.", + 'get_filename_component(_OR_DEPS "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)', + "if(TARGET openroad_deps::pool)", + " return()", + "endif()", + "add_library(openroad_deps::pool INTERFACE IMPORTED GLOBAL)", + "set(_or_deps_pool", + " -Wl,--start-group", + " -Wl,--whole-archive", + ] + for dest in alwayslink_dests: + lines.append(' "${_OR_DEPS}/' + dest + '"') + lines.append(" -Wl,--no-whole-archive") + for dest in pool_dests: + lines.append(' "${_OR_DEPS}/' + dest + '"') + lines += [ + " -Wl,--end-group", + ")", + "set_target_properties(openroad_deps::pool PROPERTIES", + ' INTERFACE_LINK_LIBRARIES "${_or_deps_pool}"', + ")", + ] + return "\n".join(lines) + "\n" + +def _shim_package(basename): + for suffix in ["ConfigVersion.cmake", "Config.cmake"]: + if basename.endswith(suffix): + return basename[:-len(suffix)] + fail("shim file name must end in Config.cmake or ConfigVersion.cmake: " + basename) + +def _cmake_deps_bundle_impl(ctx): + cc_toolchain = find_cc_toolchain(ctx) + toolchain = _toolchain_command_lines(ctx, cc_toolchain) + static_runtimes = cc_toolchain.static_runtime_lib( + feature_configuration = toolchain.feature_configuration, + ) + + copies = {} # dest -> src exec path + inputs = [cc_toolchain.all_files, static_runtimes] + + # --- Hermetic clang toolchain, staged exactly as bazel stages it. --- + for f in cc_toolchain.all_files.to_list() + static_runtimes.to_list(): + copies["llvm/" + _map_exec_path(f.path)] = f.path + + # lib/ carries the conventionally named archives (libomp.a etc.), so + # driver-added libraries like -fopenmp's -lomp resolve there. + runtime_link_args = ["-L$R/lib"] + [ + "$R/llvm/" + _map_exec_path(f.path) + for f in static_runtimes.to_list() + ] + + # --- Headers and archives of every dependency. --- + roots = {} + for dep in ctx.attr.deps: + for root in _include_roots(dep[CcInfo].compilation_context): + roots[root] = None + sorted_roots = sorted(roots.keys(), key = len, reverse = True) + + all_defines = {} + pool = {} # ordered set: dest -> None + alwayslink_pool = {} + module_archives = {} # module name -> [dest, ...] + + for dep in ctx.attr.deps: + cc_info = dep[CcInfo] + inputs.append(cc_info.compilation_context.headers) + for define in cc_info.compilation_context.defines.to_list(): + all_defines[define] = None + for header in cc_info.compilation_context.headers.to_list(): + dest = _header_dest(header, sorted_roots, ctx.attr.include_overrides) + if dest == None: + # Private header without a matching include root: not part + # of any dependency's public interface. + continue + if copies.get(dest, header.path) != header.path: + # Same header staged via several include roots (e.g. + # protobuf _virtual_includes); the assembler verifies the + # contents are identical. + continue + copies[dest] = header.path + + for linker_input in cc_info.linking_context.linker_inputs.to_list(): + for library in linker_input.libraries: + archive = library.pic_static_library or library.static_library + if archive == None: + if library.pic_objects or library.objects: + fail("{}: archive-less object list; not supported".format( + linker_input.owner, + )) + continue + inputs.append(depset([archive])) + dest = _archive_dest(archive) + copies[dest] = archive.path + module = _module_name(linker_input.owner.workspace_name) + module_archives.setdefault(module, []) + if dest not in module_archives[module]: + module_archives[module].append(dest) + if module in ctx.attr.exclude_from_pool: + continue + if library.alwayslink: + alwayslink_pool[dest] = None + else: + pool[dest] = None + + # Conventionally named copies for find_library-based CMake modules. + for module, name in ctx.attr.lib_name_overrides.items(): + archives = module_archives.get(module) + if not archives: + fail("lib_name_overrides: no archive found for module " + module) + if len(archives) > 1: + fail("lib_name_overrides: module {} has {} archives".format( + module, + len(archives), + )) + copies["lib/lib{}.a".format(name)] = copies[archives[0]] + + def single_archive(module): + archives = module_archives.get(module) + if not archives or len(archives) != 1: + fail("expected exactly one archive for module " + module) + return archives[0] + + # --- Tcl script library (init.tcl etc.), needed at openroad runtime. --- + for f in ctx.files.tcl_library: + rel = _repo_relative_path(f) + _, _, in_library = rel.partition("library/") + copies["lib/tcl9.0/" + (in_library or rel)] = f.path + inputs.append(depset(ctx.files.tcl_library)) + + # --- SWIG: binary and runtime library tree. --- + swig_binary = ctx.executable.swig + copies["tools/swig/swig"] = swig_binary.path + for f in ctx.files.swig_lib: + rel = _repo_relative_path(f) + _, _, in_lib = rel.partition("Lib/") + copies["share/swig/" + (in_lib or rel)] = f.path + inputs.append(depset(ctx.files.swig_lib + [swig_binary])) + + # --- bison / flex from their toolchains. --- + # Their env values point into the tool's runfiles tree; rewrite them to + # the bundle the same way bazel/bison.bzl rewrites them for actions: + # data files live at their source paths, binaries under bin. + bison = ctx.toolchains["@rules_bison//bison:toolchain_type"].bison_toolchain + flex = ctx.toolchains["@rules_flex//flex:toolchain_type"].flex_toolchain + tool_wrappers = {} + for name, info in [("bison", bison), ("flex", flex)]: + tool = getattr(info, name + "_tool").executable + env = dict(getattr(info, name + "_env")) + inputs.append(info.all_files) + for f in info.all_files.to_list(): + copies["tools/" + _map_exec_path(f.path)] = f.path + runfiles_dir = "{}.runfiles/{}".format(tool.path, tool.owner.workspace_name) + source_form = "$R/tools/external/" + tool.owner.workspace_name + binary_form = "$R/tools/" + _map_exec_path( + "{}/external/{}".format(tool.root.path, tool.owner.workspace_name), + ) + for key, value in env.items(): + form = source_form if key == "BISON_PKGDATADIR" else binary_form + env[key] = value.replace(runfiles_dir, form) + tool_wrappers[name] = _tool_wrapper_script( + "$R/tools/" + _map_exec_path(tool.path), + env, + ) + + # --- Hermetic CPython: interpreter, stdlib, headers, libpython. --- + py_runtime = ctx.toolchains["@rules_python//python:toolchain_type"].py3_runtime + python_dynamic_libs = [ + library.dynamic_library + for linker_input in ctx.attr.python_libs[CcInfo].linking_context.linker_inputs.to_list() + for library in linker_input.libraries + if library.dynamic_library + ] + python_files = depset( + transitive = [ + py_runtime.files, + ctx.attr.python_headers[CcInfo].compilation_context.headers, + ], + ) + for f in python_files.to_list(): + rel = _repo_relative_path(f) + if rel == f.path: + # Not inside the python repository (e.g. bazel's _solib copy + # of libpython, staged by basename below). + continue + copies["python/" + rel] = f.path + + # FindPython3's Development component wants the unversioned dev name + # (libpython3.13.so); the cc_libs artifact carries exactly that. + for f in python_dynamic_libs: + copies["python/lib/" + f.basename] = f.path + inputs.append(depset(python_dynamic_libs, transitive = [python_files])) + version_info = py_runtime.interpreter_version_info + python_version = "{}.{}".format(version_info.major, version_info.minor) + + # --- CMake package config shims. --- + for f in ctx.files.shims: + copies["lib/cmake/{}/{}".format(_shim_package(f.basename), f.basename)] = f.path + inputs.append(depset(ctx.files.shims)) + + # --- Generated text files. --- + defines = sorted(all_defines.keys()) + writes = [ + struct( + dest = "lib/cmake/openroad_deps/deps-pool.cmake", + content = _pool_cmake(pool.keys(), alwayslink_pool.keys()), + executable = False, + ), + struct( + dest = "bin/cc", + content = _wrapper_script( + toolchain.c_compiler, + [_rewrite_flag(f, "llvm") for f in toolchain.c_flags], + [_rewrite_flag(f, "llvm") for f in toolchain.link_flags] + + runtime_link_args, + defines, + ), + executable = True, + ), + struct( + dest = "bin/c++", + content = _wrapper_script( + toolchain.cxx_compiler, + [_rewrite_flag(f, "llvm") for f in toolchain.cxx_flags], + [_rewrite_flag(f, "llvm") for f in toolchain.link_flags] + + runtime_link_args, + defines, + ), + executable = True, + ), + struct( + dest = "bin/swig", + content = _tool_wrapper_script( + "$R/tools/swig/swig", + {"SWIG_LIB": "$R/share/swig"}, + ), + executable = True, + ), + struct( + dest = "bin/bison", + content = tool_wrappers["bison"], + executable = True, + ), + struct( + dest = "bin/flex", + content = tool_wrappers["flex"], + executable = True, + ), + ] + + manifest = struct( + copies = [struct(dest = dest, src = src) for dest, src in copies.items()], + writes = writes, + module_bazel = ctx.file.versions_src.path, + versions_dest = "lib/cmake/openroad_deps/deps-versions.cmake", + template_src = ctx.file.toolchain_template.path, + template_dest = "toolchain.cmake", + substitutions = { + "@ARCHIVER@": "${_OR_DEPS}/llvm/" + _map_exec_path(toolchain.archiver), + "@TCL_ARCHIVE@": "${_OR_DEPS}/" + single_archive("tcl_lang"), + "@OMP_ARCHIVE@": "${_OR_DEPS}/" + single_archive("openmp"), + "@PYTHON_VERSION@": python_version, + }, + ) + inputs.append(depset([ctx.file.versions_src, ctx.file.toolchain_template])) + + manifest_file = ctx.actions.declare_file(ctx.label.name + "-manifest.json") + ctx.actions.write(manifest_file, json.encode_indent(manifest)) + + bundle = ctx.actions.declare_directory(ctx.label.name) + ctx.actions.run( + outputs = [bundle], + inputs = depset([manifest_file], transitive = inputs), + executable = ctx.executable._assembler, + arguments = [manifest_file.path, bundle.path], + mnemonic = "CMakeDepsBundle", + progress_message = "Assembling CMake dependency prefix %{output}", + ) + + return [DefaultInfo(files = depset([bundle]))] + +cmake_deps_bundle = rule( + implementation = _cmake_deps_bundle_impl, + doc = "Assemble a CMake dependency prefix from bazel-built dependencies.", + attrs = { + "deps": attr.label_list( + providers = [CcInfo], + doc = "Dependencies whose headers and archives go into the prefix.", + ), + "exclude_from_pool": attr.string_list( + doc = "Module names whose archives are staged but kept out of " + + "the link pool (e.g. googletest, linked per-test).", + ), + "include_overrides": attr.string_dict( + doc = "Module name -> include/ subdirectory. Diverts a " + + "repository's headers to avoid basename collisions " + + "(e.g. cudd's util.h).", + ), + "lib_name_overrides": attr.string_dict( + doc = "Module name -> conventional archive name. Creates " + + "lib/lib.a copies for CMake Find modules that " + + "search by name (FindZLIB: z, FindCUDD: cudd).", + ), + "python_headers": attr.label( + providers = [CcInfo], + mandatory = True, + doc = "CPython headers (rules_python current_py_cc_headers).", + ), + "python_libs": attr.label( + providers = [CcInfo], + mandatory = True, + doc = "libpython (rules_python current_py_cc_libs).", + ), + "shims": attr.label_list( + allow_files = [".cmake"], + doc = "CMake package config shims, staged by basename into " + + "lib/cmake//.", + ), + "swig": attr.label( + executable = True, + cfg = "exec", + mandatory = True, + doc = "The swig binary.", + ), + "swig_lib": attr.label_list( + allow_files = True, + doc = "SWIG runtime library (Lib/ tree).", + ), + "tcl_library": attr.label( + allow_files = True, + mandatory = True, + doc = "The Tcl script library (@tcl_lang//:tcl_core).", + ), + "toolchain_template": attr.label( + allow_single_file = True, + mandatory = True, + doc = "toolchain.cmake template, staged at the prefix root.", + ), + "versions_src": attr.label( + allow_single_file = True, + mandatory = True, + doc = "MODULE.bazel, parsed for dependency versions.", + ), + "_assembler": attr.label( + default = ":assemble_bundle", + executable = True, + cfg = "exec", + ), + }, + fragments = ["cpp"], + toolchains = use_cc_toolchain() + [ + "@rules_bison//bison:toolchain_type", + "@rules_flex//flex:toolchain_type", + "@rules_python//python:toolchain_type", + ], +) diff --git a/cmake-deps/materialize.sh b/cmake-deps/materialize.sh new file mode 100755 index 00000000000..11add4f1391 --- /dev/null +++ b/cmake-deps/materialize.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +set -eu + +# --- begin runfiles.bash initialization v3 --- +set -uo pipefail +set +e +f=bazel_tools/tools/bash/runfiles/runfiles.bash +# shellcheck disable=SC1090 +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- + +# Materialize the CMake dependency prefix built by //cmake-deps:bundle into +# the workspace, so OpenROAD can be built with plain CMake: +# +# bazelisk run //:cmake +# cmake -DCMAKE_TOOLCHAIN_FILE=deps/toolchain.cmake -B build . +# cmake --build build -j +# +# The destination is wiped and recreated on every run; a stamp file guards +# against deleting a directory this script did not create. + +BUNDLE="$(rlocation openroad/cmake-deps/bundle)" +if [ -z "$BUNDLE" ] || [ ! -d "$BUNDLE" ]; then + echo >&2 "ERROR: bundle runfiles directory not found" + exit 1 +fi + +DEST="${1:-${BUILD_WORKSPACE_DIRECTORY}/deps}" +STAMP="$DEST/.openroad-deps-stamp" + +if [ -e "$DEST" ] && [ ! -e "$STAMP" ]; then + echo >&2 "ERROR: $DEST exists but was not created by this tool" \ + "(missing $STAMP); refusing to delete it." + exit 1 +fi + +rm -rf "$DEST" +mkdir -p "$DEST" + +# "$BUNDLE/." resolves the runfiles symlink to the bundle tree itself; +# the copy then preserves the relative symlinks the assembler creates to +# recreate the multiplexed LLVM tool names (they resolve inside the tree, +# so deps/ stays self-contained). +cp -r "$BUNDLE/." "$DEST/" +chmod -R u+w "$DEST" +touch "$STAMP" + +ABS_DEST="$(realpath "$DEST")" +echo "" +echo "CMake dependency prefix ready: $ABS_DEST" +echo "" +echo "Build OpenROAD with plain CMake:" +echo "" +echo " cmake -DCMAKE_TOOLCHAIN_FILE=$ABS_DEST/toolchain.cmake -B build ." +echo " cmake --build build -j\$(nproc)" +echo "" +echo "Run the result with the bundled Tcl and Python runtimes:" +echo "" +echo " export TCL_LIBRARY=$ABS_DEST/lib/tcl9.0" +echo " export PYTHONHOME=$ABS_DEST/python" +echo " ./build/src/openroad" +echo "" +echo "Host requirements: cmake >= 3.16, ninja or make, git, bash." +echo "Linux x86_64 only; the GUI is not part of this prefix." diff --git a/cmake-deps/shims/BoostConfig.cmake b/cmake-deps/shims/BoostConfig.cmake new file mode 100644 index 00000000000..34e4b323b7d --- /dev/null +++ b/cmake-deps/shims/BoostConfig.cmake @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors +# +# Config shim for the bazel-materialized Boost (modular BCR build). +# Compiled Boost objects live in the shared archive pool, so every +# component target is an interface over the merged include tree + pool. + +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") + +set(Boost_VERSION "${OPENROAD_DEPS_VERSION_boost}") +set(Boost_VERSION_STRING "${OPENROAD_DEPS_VERSION_boost}") +set(Boost_INCLUDE_DIRS "${_OR_DEPS}/include") + +if(NOT TARGET Boost::headers) + add_library(Boost::headers INTERFACE IMPORTED GLOBAL) + set_target_properties(Boost::headers PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" + ) + add_library(Boost::boost INTERFACE IMPORTED GLOBAL) + set_target_properties(Boost::boost PROPERTIES + INTERFACE_LINK_LIBRARIES Boost::headers + ) +endif() + +set(Boost_LIBRARIES "") +foreach(_or_boost_comp IN LISTS Boost_FIND_COMPONENTS) + if(NOT TARGET Boost::${_or_boost_comp}) + add_library(Boost::${_or_boost_comp} INTERFACE IMPORTED GLOBAL) + set_target_properties(Boost::${_or_boost_comp} PROPERTIES + INTERFACE_LINK_LIBRARIES "Boost::headers;openroad_deps::pool" + ) + endif() + set(Boost_${_or_boost_comp}_FOUND TRUE) + list(APPEND Boost_LIBRARIES Boost::${_or_boost_comp}) +endforeach() +unset(_or_boost_comp) diff --git a/cmake-deps/shims/BoostConfigVersion.cmake b/cmake-deps/shims/BoostConfigVersion.cmake new file mode 100644 index 00000000000..cb4e28608dd --- /dev/null +++ b/cmake-deps/shims/BoostConfigVersion.cmake @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors +# +# AnyNewerVersion semantics for the Boost config shim. + +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") + +set(PACKAGE_VERSION "${OPENROAD_DEPS_VERSION_boost}") +if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() diff --git a/cmake-deps/shims/Eigen3Config.cmake b/cmake-deps/shims/Eigen3Config.cmake new file mode 100644 index 00000000000..bae7ae9ac3a --- /dev/null +++ b/cmake-deps/shims/Eigen3Config.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors +# +# Config shim for bazel-materialized Eigen (header-only). + +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") + +set(Eigen3_VERSION "${OPENROAD_DEPS_VERSION_eigen}") +set(EIGEN3_INCLUDE_DIR "${_OR_DEPS}/include") +set(EIGEN3_INCLUDE_DIRS "${_OR_DEPS}/include") + +if(NOT TARGET Eigen3::Eigen) + add_library(Eigen3::Eigen INTERFACE IMPORTED GLOBAL) + set_target_properties(Eigen3::Eigen PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" + ) +endif() diff --git a/cmake-deps/shims/GTestConfig.cmake b/cmake-deps/shims/GTestConfig.cmake new file mode 100644 index 00000000000..126be84623f --- /dev/null +++ b/cmake-deps/shims/GTestConfig.cmake @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors +# +# Config shim for bazel-materialized GoogleTest. The BCR build folds gmock +# into libgtest.a. These archives are deliberately not in the shared pool: +# gtest_main carries main(). + +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") + +set(GTest_VERSION "${OPENROAD_DEPS_VERSION_googletest}") + +if(NOT TARGET GTest::gtest) + add_library(GTest::gtest STATIC IMPORTED GLOBAL) + set_target_properties(GTest::gtest PROPERTIES + IMPORTED_LOCATION "${_OR_DEPS}/lib/pool/googletest+/libgtest.a" + INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" + INTERFACE_LINK_LIBRARIES openroad_deps::pool + ) + add_library(GTest::gtest_main STATIC IMPORTED GLOBAL) + set_target_properties(GTest::gtest_main PROPERTIES + IMPORTED_LOCATION "${_OR_DEPS}/lib/pool/googletest+/libgtest_main.a" + INTERFACE_LINK_LIBRARIES GTest::gtest + ) + add_library(GTest::gmock ALIAS GTest::gtest) + add_library(GTest::gmock_main ALIAS GTest::gtest_main) + add_library(GTest::GTest ALIAS GTest::gtest) + add_library(GTest::Main ALIAS GTest::gtest_main) +endif() diff --git a/cmake-deps/shims/LEMONConfig.cmake b/cmake-deps/shims/LEMONConfig.cmake new file mode 100644 index 00000000000..8259da12c27 --- /dev/null +++ b/cmake-deps/shims/LEMONConfig.cmake @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors +# +# Config shim for bazel-materialized COIN-OR LEMON. OpenROAD only consumes +# ${LEMON_INCLUDE_DIRS}; the compiled objects are in the archive pool. + +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") + +set(LEMON_VERSION "${OPENROAD_DEPS_VERSION_coin_or_lemon}") +set(LEMON_INCLUDE_DIR "${_OR_DEPS}/include") +set(LEMON_INCLUDE_DIRS "${_OR_DEPS}/include") +set(LEMON_LIBRARIES openroad_deps::pool) diff --git a/cmake-deps/shims/abslConfig.cmake b/cmake-deps/shims/abslConfig.cmake new file mode 100644 index 00000000000..46e7f1b929a --- /dev/null +++ b/cmake-deps/shims/abslConfig.cmake @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors +# +# Config shim for bazel-materialized Abseil. Only the targets OpenROAD's +# CMake files reference are defined; all are interfaces over the merged +# include tree + archive pool. + +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") + +foreach(_or_absl_target city hash strings synchronization base time span) + if(NOT TARGET absl::${_or_absl_target}) + add_library(absl::${_or_absl_target} INTERFACE IMPORTED GLOBAL) + set_target_properties(absl::${_or_absl_target} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" + INTERFACE_LINK_LIBRARIES openroad_deps::pool + ) + endif() +endforeach() +unset(_or_absl_target) diff --git a/cmake-deps/shims/fmtConfig.cmake b/cmake-deps/shims/fmtConfig.cmake new file mode 100644 index 00000000000..d0e840a7904 --- /dev/null +++ b/cmake-deps/shims/fmtConfig.cmake @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors +# +# Config shim for bazel-materialized fmt. + +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") + +set(fmt_VERSION "${OPENROAD_DEPS_VERSION_fmt}") + +if(NOT TARGET fmt::fmt) + add_library(fmt::fmt INTERFACE IMPORTED GLOBAL) + set_target_properties(fmt::fmt PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" + INTERFACE_LINK_LIBRARIES openroad_deps::pool + ) + add_library(fmt::fmt-header-only INTERFACE IMPORTED GLOBAL) + set_target_properties(fmt::fmt-header-only PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" + INTERFACE_COMPILE_DEFINITIONS FMT_HEADER_ONLY=1 + ) +endif() diff --git a/cmake-deps/shims/ortoolsConfig.cmake b/cmake-deps/shims/ortoolsConfig.cmake new file mode 100644 index 00000000000..b9ec1ab7284 --- /dev/null +++ b/cmake-deps/shims/ortoolsConfig.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors +# +# Config shim for bazel-materialized OR-Tools (with scip, highs, glpk, +# soplex, bliss, protobuf and abseil in the archive pool). + +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") + +set(ortools_VERSION "${OPENROAD_DEPS_VERSION_or_tools}") + +if(NOT TARGET ortools::ortools) + add_library(ortools::ortools INTERFACE IMPORTED GLOBAL) + set_target_properties(ortools::ortools PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" + INTERFACE_LINK_LIBRARIES openroad_deps::pool + ) +endif() diff --git a/cmake-deps/shims/spdlogConfig.cmake b/cmake-deps/shims/spdlogConfig.cmake new file mode 100644 index 00000000000..1945a04d34d --- /dev/null +++ b/cmake-deps/shims/spdlogConfig.cmake @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors +# +# Config shim for bazel-materialized spdlog (header-only in the bazel +# graph, with external fmt; SPDLOG_FMT_EXTERNAL is injected globally by +# the compiler wrappers). + +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") + +set(spdlog_VERSION "${OPENROAD_DEPS_VERSION_spdlog}") + +if(NOT TARGET spdlog::spdlog) + add_library(spdlog::spdlog INTERFACE IMPORTED GLOBAL) + set_target_properties(spdlog::spdlog PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" + INTERFACE_LINK_LIBRARIES openroad_deps::pool + ) + add_library(spdlog::spdlog_header_only ALIAS spdlog::spdlog) +endif() diff --git a/cmake-deps/shims/yaml-cppConfig.cmake b/cmake-deps/shims/yaml-cppConfig.cmake new file mode 100644 index 00000000000..fdf74b53672 --- /dev/null +++ b/cmake-deps/shims/yaml-cppConfig.cmake @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors +# +# Config shim for bazel-materialized yaml-cpp. Upstream's config defines +# the plain `yaml-cpp` target (odb/3dblox links it) plus the namespaced +# alias. + +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") + +set(yaml-cpp_VERSION "${OPENROAD_DEPS_VERSION_yaml_cpp}") + +if(NOT TARGET yaml-cpp) + add_library(yaml-cpp INTERFACE IMPORTED GLOBAL) + set_target_properties(yaml-cpp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" + INTERFACE_LINK_LIBRARIES openroad_deps::pool + ) + add_library(yaml-cpp::yaml-cpp ALIAS yaml-cpp) +endif() diff --git a/cmake-deps/toolchain.cmake.in b/cmake-deps/toolchain.cmake.in new file mode 100644 index 00000000000..022bd211be0 --- /dev/null +++ b/cmake-deps/toolchain.cmake.in @@ -0,0 +1,92 @@ +# Generated by //cmake-deps (template: cmake-deps/toolchain.cmake.in). +# +# CMake toolchain file for building OpenROAD against the bazel-materialized +# dependency prefix this file sits in. Usage: +# +# bazelisk run //:cmake +# cmake -DCMAKE_TOOLCHAIN_FILE=deps/toolchain.cmake -B build . +# cmake --build build -j +# +# Every path is relative to this file, so the prefix is relocatable. + +get_filename_component(_OR_DEPS "${CMAKE_CURRENT_LIST_DIR}" ABSOLUTE) + +# --- Hermetic clang/libc++ toolchain ----------------------------------- +# bin/cc and bin/c++ wrap the statically linked LLVM distribution with the +# exact flags the bazel build uses (zero sysroot: the toolchain's own +# libc++, compiler-rt, glibc 2.28 headers and stub libraries; the produced +# binaries run against the host glibc). The dependency archives below are +# libc++ builds, so a host GCC/libstdc++ compiler cannot link them. +set(CMAKE_C_COMPILER "${_OR_DEPS}/bin/cc") +set(CMAKE_CXX_COMPILER "${_OR_DEPS}/bin/c++") +set(CMAKE_AR "@ARCHIVER@" CACHE FILEPATH "") + +# llvm-ar's 's' flag writes the symbol table; no separate ranlib step. +set(CMAKE_C_ARCHIVE_CREATE " rcs ") +set(CMAKE_C_ARCHIVE_APPEND " rs ") +set(CMAKE_C_ARCHIVE_FINISH "") +set(CMAKE_CXX_ARCHIVE_CREATE " rcs ") +set(CMAKE_CXX_ARCHIVE_APPEND " rs ") +set(CMAKE_CXX_ARCHIVE_FINISH "") + +# The bazel build is C++20 (see .bazelrc); with libc++ this also provides +# std::format, which OpenSTA prefers over external fmt. +set(CMAKE_CXX_STANDARD 20 CACHE STRING "") + +# --- Dependency resolution ---------------------------------------------- +list(PREPEND CMAKE_PREFIX_PATH "${_OR_DEPS}") + +# Let the config shims in lib/cmake/ win over CMake's Find modules +# (FindGTest would otherwise probe the host). +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) + +# Legacy DependencyInstaller.sh prefixes; anything found there would be a +# GCC/libstdc++ build. +set(CMAKE_IGNORE_PREFIX_PATH "/usr/local;/opt") + +# Tcl: preset the two cache variables cmake/FindTCL.cmake probes for. +# The archive is Tcl 9; its script library is staged at lib/tcl9.0 +# (export TCL_LIBRARY=/lib/tcl9.0 at runtime). +set(TCL_LIBRARY "@TCL_ARCHIVE@" CACHE FILEPATH "") +set(TCL_HEADER "${_OR_DEPS}/include/tcl.h" CACHE FILEPATH "") + +# Build tools. +set(SWIG_EXECUTABLE "${_OR_DEPS}/bin/swig" CACHE FILEPATH "") +set(SWIG_DIR "${_OR_DEPS}/share/swig" CACHE PATH "") +set(BISON_EXECUTABLE "${_OR_DEPS}/bin/bison" CACHE FILEPATH "") +set(FLEX_EXECUTABLE "${_OR_DEPS}/bin/flex" CACHE FILEPATH "") +set(FLEX_INCLUDE_DIR "${_OR_DEPS}/include" CACHE PATH "") + +# Hermetic CPython (interpreter, headers, libpython, stdlib). The +# unversioned Python_* variables cover slang's build-time codegen, which +# uses FindPython rather than FindPython3. +set(Python3_ROOT_DIR "${_OR_DEPS}/python") +set(Python3_EXECUTABLE "${_OR_DEPS}/python/bin/python3" CACHE FILEPATH "") +set(Python3_FIND_STRATEGY LOCATION) +set(Python_ROOT_DIR "${_OR_DEPS}/python") +set(Python_EXECUTABLE "${_OR_DEPS}/python/bin/python3" CACHE FILEPATH "") +set(Python_FIND_STRATEGY LOCATION) + +# zlib and CUDD are found by their Find modules under these roots. +set(ZLIB_ROOT "${_OR_DEPS}") +set(cudd_ROOT "${_OR_DEPS}") +set(CUDD_ROOT "${_OR_DEPS}") + +# OpenMP: short-circuit FindOpenMP's probing. The compiler wrapper adds +# -L/lib when linking, which resolves the driver-added -lomp. +set(OpenMP_C_FLAGS "-fopenmp -I${_OR_DEPS}/include" CACHE STRING "") +set(OpenMP_CXX_FLAGS "${OpenMP_C_FLAGS}" CACHE STRING "") +set(OpenMP_C_LIB_NAMES "omp" CACHE STRING "") +set(OpenMP_CXX_LIB_NAMES "omp" CACHE STRING "") +set(OpenMP_omp_LIBRARY "${_OR_DEPS}/lib/libomp.a" CACHE FILEPATH "") + +# The openroad binary embeds the hermetic CPython via libpython.so. +set(CMAKE_BUILD_RPATH "${_OR_DEPS}/python/lib") +set(CMAKE_INSTALL_RPATH "${_OR_DEPS}/python/lib") + +# Host Qt5 is a GCC/libstdc++ build and cannot link into this libc++ +# toolchain; the GUI is not part of the prefix (the build degrades to CLI, +# exactly like a host without Qt). +set(BUILD_GUI OFF CACHE BOOL "") + +include("${_OR_DEPS}/lib/cmake/openroad_deps/deps-versions.cmake") diff --git a/docs/user/Build.md b/docs/user/Build.md index 60675b30d66..6c8a34c7d13 100644 --- a/docs/user/Build.md +++ b/docs/user/Build.md @@ -95,6 +95,40 @@ it can be uploaded in the "Relevant log output" section of OpenROAD [issue forms](https://github.com/The-OpenROAD-Project/OpenROAD-flow-scripts/issues/new/choose). ``` +### Dependencies from Bazel (no DependencyInstaller.sh) + +On Linux x86_64, the pinned dependency set of the Bazel build can be +materialized into a local `deps/` folder and used for a plain CMake +build — no `sudo`, no distro packages beyond `cmake`, `ninja` (or +`make`), `git` and `bash`, and no compiler: `deps/` includes the same +hermetic clang/libc++ toolchain the Bazel build uses. + +``` shell +bazelisk run //:cmake +cmake -DCMAKE_TOOLCHAIN_FILE=deps/toolchain.cmake -B build . +cmake --build build -j$(nproc) +``` + +The resulting binary uses the bundled Tcl and Python runtimes: + +``` shell +export TCL_LIBRARY=$PWD/deps/lib/tcl9.0 +export PYTHONHOME=$PWD/deps/python +./build/src/openroad +``` + +Notes: + +- `deps/` is about 1 GB and is fully regenerated on every + `bazelisk run //:cmake`; dependency versions follow `MODULE.bazel`. +- The dependency archives are libc++ builds; `deps/toolchain.cmake` + selects the bundled clang. A host GCC/libstdc++ toolchain cannot link + them. +- The GUI is not part of the prefix (host Qt5 is a libstdc++ build); + the build degrades to CLI, like a host without Qt. +- The existing `DependencyInstaller.sh`/`Build.sh` flow is unaffected; + this is an alternative, not a replacement. + ### Only for macOS Setup On macOS, it is recommended to use a Python virtual environment to isolate dependencies and avoid system conflicts. diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ed964f419ff..d95f42a62d8 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -7,7 +7,12 @@ option(BUILD_GUI "Build the GUI" ON) # If Qt is not installed there will not be cmake # support for the package so this needs to be "quiet". find_package(Qt5 QUIET COMPONENTS Core Widgets Charts) -find_package(OpenGL REQUIRED) + +# OpenGL is only linked by the Qt GUI (OpenGL::GL below), so only require +# it when the GUI is actually built. +if (Qt5_FOUND AND BUILD_GUI) + find_package(OpenGL REQUIRED) +endif() include("openroad") set(CMAKE_INCLUDE_CURRENT_DIR ON) From 5c560d3edc730a306957e46798971c942e938baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 6 Jul 2026 09:11:11 +0200 Subject: [PATCH 02/14] cmake-deps: refuse to run without BUILD_WORKSPACE_DIRECTORY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the variable unset or empty (the script run directly rather than via bazel run), the default destination degenerated to /deps. The stamp check would still have refused to delete anything, but fail early with a clear message instead of relying on that backstop. Review feedback from gemini-code-assist on #10822. Signed-off-by: Øyvind Harboe --- cmake-deps/materialize.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake-deps/materialize.sh b/cmake-deps/materialize.sh index 11add4f1391..0e81559cba9 100755 --- a/cmake-deps/materialize.sh +++ b/cmake-deps/materialize.sh @@ -31,7 +31,10 @@ if [ -z "$BUNDLE" ] || [ ! -d "$BUNDLE" ]; then exit 1 fi -DEST="${1:-${BUILD_WORKSPACE_DIRECTORY}/deps}" +# :? guards against BUILD_WORKSPACE_DIRECTORY being unset or empty (e.g. +# the script run directly instead of via bazel run), where the default +# would otherwise resolve to /deps. +DEST="${1:-${BUILD_WORKSPACE_DIRECTORY:?not set; run this via: bazelisk run //:cmake}/deps}" STAMP="$DEST/.openroad-deps-stamp" if [ -e "$DEST" ] && [ ! -e "$STAMP" ]; then From 2ee38dc83034f65de70045ab4542dc38f9d2eb6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 6 Jul 2026 09:11:52 +0200 Subject: [PATCH 03/14] cmake-deps: reference gtest archives by stable lib/ paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GTestConfig.cmake embedded the bzlmod canonical repository name (lib/pool/googletest+/...), which is not stable across bazel versions. A new stable_lib_modules attribute copies a module's archives to lib/; the shim now points at lib/libgtest.a and lib/libgtest_main.a. The generated deps-pool.cmake is unaffected: it is produced from the actual archive paths at build time. Review feedback from gemini-code-assist on #10822. Signed-off-by: Øyvind Harboe --- cmake-deps/BUILD.bazel | 2 ++ cmake-deps/defs.bzl | 16 ++++++++++++++++ cmake-deps/shims/GTestConfig.cmake | 4 ++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cmake-deps/BUILD.bazel b/cmake-deps/BUILD.bazel index c7f6f400c69..8e330d9a7dc 100644 --- a/cmake-deps/BUILD.bazel +++ b/cmake-deps/BUILD.bazel @@ -90,6 +90,8 @@ cmake_deps_bundle( "zlib": "z", }, python_headers = "@rules_python//python/cc:current_py_cc_headers", + # GTestConfig.cmake references these archives by stable lib/ paths. + stable_lib_modules = ["googletest"], python_libs = "@rules_python//python/cc:current_py_cc_libs", shims = glob(["shims/*.cmake"]), swig = "@swig", diff --git a/cmake-deps/defs.bzl b/cmake-deps/defs.bzl index 15f7be09a67..f4ef5c6c6ec 100644 --- a/cmake-deps/defs.bzl +++ b/cmake-deps/defs.bzl @@ -378,6 +378,17 @@ def _cmake_deps_bundle_impl(ctx): )) copies["lib/lib{}.a".format(name)] = copies[archives[0]] + # Modules whose archives the static shims reference by path: copy them + # to lib/ so the shims need no bzlmod canonical repository + # names (lib/pool/ paths embed e.g. "googletest+", which is not stable + # across bazel versions). + for module in ctx.attr.stable_lib_modules: + for dest in module_archives.get(module, []): + basename = dest.split("/")[-1] + if copies.get("lib/" + basename, copies[dest]) != copies[dest]: + fail("stable_lib_modules: lib/{} already taken".format(basename)) + copies["lib/" + basename] = copies[dest] + def single_archive(module): archives = module_archives.get(module) if not archives or len(archives) != 1: @@ -574,6 +585,11 @@ cmake_deps_bundle = rule( mandatory = True, doc = "libpython (rules_python current_py_cc_libs).", ), + "stable_lib_modules": attr.string_list( + doc = "Module names whose archives are also copied to " + + "lib/, giving the static config shims paths " + + "free of bzlmod canonical repository names.", + ), "shims": attr.label_list( allow_files = [".cmake"], doc = "CMake package config shims, staged by basename into " + diff --git a/cmake-deps/shims/GTestConfig.cmake b/cmake-deps/shims/GTestConfig.cmake index 126be84623f..0e5a2edbdce 100644 --- a/cmake-deps/shims/GTestConfig.cmake +++ b/cmake-deps/shims/GTestConfig.cmake @@ -13,13 +13,13 @@ set(GTest_VERSION "${OPENROAD_DEPS_VERSION_googletest}") if(NOT TARGET GTest::gtest) add_library(GTest::gtest STATIC IMPORTED GLOBAL) set_target_properties(GTest::gtest PROPERTIES - IMPORTED_LOCATION "${_OR_DEPS}/lib/pool/googletest+/libgtest.a" + IMPORTED_LOCATION "${_OR_DEPS}/lib/libgtest.a" INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" INTERFACE_LINK_LIBRARIES openroad_deps::pool ) add_library(GTest::gtest_main STATIC IMPORTED GLOBAL) set_target_properties(GTest::gtest_main PROPERTIES - IMPORTED_LOCATION "${_OR_DEPS}/lib/pool/googletest+/libgtest_main.a" + IMPORTED_LOCATION "${_OR_DEPS}/lib/libgtest_main.a" INTERFACE_LINK_LIBRARIES GTest::gtest ) add_library(GTest::gmock ALIAS GTest::gtest) From b0a84f4c0e188d1df537f4b092336712a85083fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 6 Jul 2026 09:12:24 +0200 Subject: [PATCH 04/14] cmake-deps: escape literal $ in generated wrapper arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _shell_quote left $ unescaped inside double quotes, so a toolchain flag or define containing a literal $ would be expanded by bash when the wrapper runs. Escape all $ and restore only the ${R} placeholder. Review feedback from gemini-code-assist on #10822. Signed-off-by: Øyvind Harboe --- cmake-deps/defs.bzl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cmake-deps/defs.bzl b/cmake-deps/defs.bzl index f4ef5c6c6ec..6a8e54055e5 100644 --- a/cmake-deps/defs.bzl +++ b/cmake-deps/defs.bzl @@ -103,9 +103,18 @@ def _shell_quote(arg): """Double-quote for bash, keeping ${R} expandable. Toolchain flags contain literal quotes (-D__DATE__="redacted") that - must survive into the macro value. + must survive into the macro value. Any other literal $ is escaped so + bash does not expand it; only the ${R} placeholder stays live. """ - return '"' + arg.replace("\\", "\\\\").replace('"', '\\"').replace("`", "\\`") + '"' + escaped = ( + arg + .replace("\\", "\\\\") + .replace('"', '\\"') + .replace("`", "\\`") + .replace("$", "\\$") + .replace("\\${R}", "${R}") + ) + return '"' + escaped + '"' def _module_name(workspace_name): """Bazel module name of a canonical repository name. From 6666d4c1f3eb3fd7874709f6d5aab5be86fa9a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 6 Jul 2026 09:12:45 +0200 Subject: [PATCH 05/14] cmake-deps: use context managers for file reads in the assembler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback from gemini-code-assist on #10822. Signed-off-by: Øyvind Harboe --- cmake-deps/assemble_bundle.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake-deps/assemble_bundle.py b/cmake-deps/assemble_bundle.py index 455408e1c24..db47bc5c343 100644 --- a/cmake-deps/assemble_bundle.py +++ b/cmake-deps/assemble_bundle.py @@ -76,7 +76,8 @@ def write(self, dest, content, executable): def parse_versions(module_bazel_path): """Dependency versions from MODULE.bazel, .bcr suffixes stripped.""" - text = open(module_bazel_path).read() + with open(module_bazel_path, encoding="utf-8") as f: + text = f.read() versions = {} variables = dict(re.findall(r'^(\w+) = "([^"]+)"', text, re.MULTILINE)) for name, version in re.findall( @@ -124,7 +125,8 @@ def main(): executable=False, ) - template = open(manifest["template_src"]).read() + with open(manifest["template_src"], encoding="utf-8") as f: + template = f.read() for key, value in manifest["substitutions"].items(): template = template.replace(key, value) unresolved = re.findall(r"@[A-Z_]+@", template) From a99838ed7b7c3c99a376eae03fd1e73508d996cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 6 Jul 2026 09:15:13 +0200 Subject: [PATCH 06/14] cmake-deps: satisfy buildifier lint and format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Load cc_common and CcInfo from @rules_cc//cc/common (native symbols are no longer global per buildifier v8), let buildifier reorder the rule attribute dict and BUILD attributes, and annotate the two intentional /external/ path strings in the bison/flex env rewrite the same way bazel/bison.bzl does. Fixes the Buildifier lint and Buildifier format CI failures on #10822. Signed-off-by: Øyvind Harboe --- cmake-deps/BUILD.bazel | 56 +++++++++++++++++++++--------------------- cmake-deps/defs.bzl | 18 ++++++++------ 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/cmake-deps/BUILD.bazel b/cmake-deps/BUILD.bazel index 8e330d9a7dc..3e174cd4ca9 100644 --- a/cmake-deps/BUILD.bazel +++ b/cmake-deps/BUILD.bazel @@ -18,6 +18,34 @@ py_binary( # through their transitive compilation contexts. cmake_deps_bundle( name = "bundle", + exclude_from_pool = ["googletest"], + include_overrides = { + # cudd's util.h etc. would collide in a flat include/; FindCUDD + # searches the include/cudd suffix. + "cudd": "cudd", + }, + lib_name_overrides = { + # FindZLIB and FindCUDD search by library name under _ROOT; + # the clang driver resolves -lomp during FindOpenMP's checks. + "cudd": "cudd", + "openmp": "omp", + "zlib": "z", + }, + python_headers = "@rules_python//python/cc:current_py_cc_headers", + python_libs = "@rules_python//python/cc:current_py_cc_libs", + shims = glob(["shims/*.cmake"]), + # GTestConfig.cmake references these archives by stable lib/ paths. + stable_lib_modules = ["googletest"], + swig = "@swig", + swig_lib = [ + "@swig//:lib_python", + "@swig//:lib_tcl", + "@swig//:swig_swg", + ], + tcl_library = "@tcl_lang//:tcl_core", + toolchain_template = "toolchain.cmake.in", + versions_src = "//:MODULE.bazel", + visibility = ["//visibility:private"], deps = [ "@abseil-cpp//absl/hash", "@abseil-cpp//absl/strings", @@ -76,34 +104,6 @@ cmake_deps_bundle( "@yaml-cpp", "@zlib", ], - exclude_from_pool = ["googletest"], - include_overrides = { - # cudd's util.h etc. would collide in a flat include/; FindCUDD - # searches the include/cudd suffix. - "cudd": "cudd", - }, - lib_name_overrides = { - # FindZLIB and FindCUDD search by library name under _ROOT; - # the clang driver resolves -lomp during FindOpenMP's checks. - "cudd": "cudd", - "openmp": "omp", - "zlib": "z", - }, - python_headers = "@rules_python//python/cc:current_py_cc_headers", - # GTestConfig.cmake references these archives by stable lib/ paths. - stable_lib_modules = ["googletest"], - python_libs = "@rules_python//python/cc:current_py_cc_libs", - shims = glob(["shims/*.cmake"]), - swig = "@swig", - swig_lib = [ - "@swig//:lib_python", - "@swig//:lib_tcl", - "@swig//:swig_swg", - ], - tcl_library = "@tcl_lang//:tcl_core", - toolchain_template = "toolchain.cmake.in", - versions_src = "//:MODULE.bazel", - visibility = ["//visibility:private"], ) # `bazelisk run //:cmake` (alias of this target) copies :bundle into diff --git a/cmake-deps/defs.bzl b/cmake-deps/defs.bzl index 6a8e54055e5..85d117d02ce 100644 --- a/cmake-deps/defs.bzl +++ b/cmake-deps/defs.bzl @@ -26,6 +26,8 @@ what bazel itself uses when the `llvm` module is bumped. load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES") load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain", "use_cc_toolchain") +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") # Flags owned by the CMake build type, not by the toolchain. Everything else # extracted from the bazel toolchain is kept verbatim. @@ -434,9 +436,9 @@ def _cmake_deps_bundle_impl(ctx): for f in info.all_files.to_list(): copies["tools/" + _map_exec_path(f.path)] = f.path runfiles_dir = "{}.runfiles/{}".format(tool.path, tool.owner.workspace_name) - source_form = "$R/tools/external/" + tool.owner.workspace_name + source_form = "$R/tools/external/" + tool.owner.workspace_name # buildifier: disable=external-path binary_form = "$R/tools/" + _map_exec_path( - "{}/external/{}".format(tool.root.path, tool.owner.workspace_name), + "{}/external/{}".format(tool.root.path, tool.owner.workspace_name), # buildifier: disable=external-path ) for key, value in env.items(): form = source_form if key == "BISON_PKGDATADIR" else binary_form @@ -540,9 +542,9 @@ def _cmake_deps_bundle_impl(ctx): template_dest = "toolchain.cmake", substitutions = { "@ARCHIVER@": "${_OR_DEPS}/llvm/" + _map_exec_path(toolchain.archiver), - "@TCL_ARCHIVE@": "${_OR_DEPS}/" + single_archive("tcl_lang"), "@OMP_ARCHIVE@": "${_OR_DEPS}/" + single_archive("openmp"), "@PYTHON_VERSION@": python_version, + "@TCL_ARCHIVE@": "${_OR_DEPS}/" + single_archive("tcl_lang"), }, ) inputs.append(depset([ctx.file.versions_src, ctx.file.toolchain_template])) @@ -594,16 +596,16 @@ cmake_deps_bundle = rule( mandatory = True, doc = "libpython (rules_python current_py_cc_libs).", ), - "stable_lib_modules": attr.string_list( - doc = "Module names whose archives are also copied to " + - "lib/, giving the static config shims paths " + - "free of bzlmod canonical repository names.", - ), "shims": attr.label_list( allow_files = [".cmake"], doc = "CMake package config shims, staged by basename into " + "lib/cmake//.", ), + "stable_lib_modules": attr.string_list( + doc = "Module names whose archives are also copied to " + + "lib/, giving the static config shims paths " + + "free of bzlmod canonical repository names.", + ), "swig": attr.label( executable = True, cfg = "exec", From 4f72defcfeb250ef15823e2933bc1d021324365d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 6 Jul 2026 09:26:41 +0200 Subject: [PATCH 07/14] cmake-deps: parse bazel_dep blocks position-independently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MODULE.bazel version parser assumed single-line bazel_dep calls with name before version; buildifier reformatting or argument reordering would silently drop versions. Match each call block as a whole and search name/version within it. Output is byte-identical for the current MODULE.bazel. Review feedback from gemini-code-assist on #10822. Signed-off-by: Øyvind Harboe --- cmake-deps/assemble_bundle.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cmake-deps/assemble_bundle.py b/cmake-deps/assemble_bundle.py index db47bc5c343..7f32251ae3c 100644 --- a/cmake-deps/assemble_bundle.py +++ b/cmake-deps/assemble_bundle.py @@ -75,19 +75,26 @@ def write(self, dest, content, executable): def parse_versions(module_bazel_path): - """Dependency versions from MODULE.bazel, .bcr suffixes stripped.""" + """Dependency versions from MODULE.bazel, .bcr suffixes stripped. + + Matches each bazel_dep(...) block as a whole, so argument order and + buildifier's line wrapping do not matter. + """ with open(module_bazel_path, encoding="utf-8") as f: text = f.read() versions = {} variables = dict(re.findall(r'^(\w+) = "([^"]+)"', text, re.MULTILINE)) - for name, version in re.findall( - r'bazel_dep\(\s*name = "([^"]+)",\s*version = ("[^"]+"|\w+)', text - ): + for block in re.findall(r"bazel_dep\s*\(([^)]+)\)", text, re.DOTALL): + name_match = re.search(r'name\s*=\s*"([^"]+)"', block) + version_match = re.search(r'version\s*=\s*("[^"]+"|\w+)', block) + if not name_match or not version_match: + continue + version = version_match.group(1) if version.startswith('"'): version = version.strip('"') else: version = variables.get(version, "") - versions[name] = version.split(".bcr.")[0] + versions[name_match.group(1)] = version.split(".bcr.")[0] for name, version in list(versions.items()): if name.startswith("boost."): versions.setdefault("boost", version) From 5cb269729f7687c1d23c4314f3f629257b70531a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 6 Jul 2026 09:27:18 +0200 Subject: [PATCH 08/14] cmake-deps: document why propagated defines are injected globally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review discussion on #10822: per-shim INTERFACE_COMPILE_DEFINITIONS cannot reach dependencies consumed through plain cache variables (Tcl, zlib), and a define set differing between a header's TU and its archive (_FILE_OFFSET_BITS=64) is an ABI break. Global injection is bazel's semantics for these defines; record that reasoning at the collection site. Signed-off-by: Øyvind Harboe --- cmake-deps/defs.bzl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmake-deps/defs.bzl b/cmake-deps/defs.bzl index 85d117d02ce..c5bad7bd3a3 100644 --- a/cmake-deps/defs.bzl +++ b/cmake-deps/defs.bzl @@ -331,6 +331,15 @@ def _cmake_deps_bundle_impl(ctx): roots[root] = None sorted_roots = sorted(roots.keys(), key = len, reverse = True) + # Propagated defines are collected across all deps and injected + # globally by the compiler wrappers. This mirrors bazel, where these + # defines reach every target that (transitively) depends on the + # dependency — for openroad, all of them. Per-package attribution via + # INTERFACE_COMPILE_DEFINITIONS on the shims cannot cover the + # dependencies CMake consumes without an imported target (Tcl and + # zlib are plain cache-variable paths), and a header compiled with a + # different define set than its archive (e.g. _FILE_OFFSET_BITS=64) + # is an ABI break. all_defines = {} pool = {} # ordered set: dest -> None alwayslink_pool = {} From cc1bbcea4f17937f4502171a73d5f578ba4eee37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 10 Jul 2026 15:18:39 +0200 Subject: [PATCH 09/14] cmake-deps: utf-8 file encodings; quote/whitespace-tolerant parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parse_versions now accepts single quotes and flexible whitespace in MODULE.bazel assignments (buildifier normalizes to double quotes, but manual edits should not silently drop versions). Output is byte-identical for the current MODULE.bazel. Review feedback from gemini-code-assist on #10872. Signed-off-by: Øyvind Harboe --- cmake-deps/assemble_bundle.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/cmake-deps/assemble_bundle.py b/cmake-deps/assemble_bundle.py index 7f32251ae3c..cb32d26f678 100644 --- a/cmake-deps/assemble_bundle.py +++ b/cmake-deps/assemble_bundle.py @@ -69,7 +69,7 @@ def copy(self, src, dest): def write(self, dest, content, executable): os.makedirs(os.path.dirname(dest), exist_ok=True) - with open(dest, "w") as f: + with open(dest, "w", encoding="utf-8") as f: f.write(content) os.chmod(dest, 0o755 if executable else 0o644) @@ -83,15 +83,17 @@ def parse_versions(module_bazel_path): with open(module_bazel_path, encoding="utf-8") as f: text = f.read() versions = {} - variables = dict(re.findall(r'^(\w+) = "([^"]+)"', text, re.MULTILINE)) + variables = dict( + re.findall(r'^\s*(\w+)\s*=\s*["\']([^"\']+)["\']', text, re.MULTILINE) + ) for block in re.findall(r"bazel_dep\s*\(([^)]+)\)", text, re.DOTALL): - name_match = re.search(r'name\s*=\s*"([^"]+)"', block) - version_match = re.search(r'version\s*=\s*("[^"]+"|\w+)', block) + name_match = re.search(r'name\s*=\s*["\']([^"\']+)["\']', block) + version_match = re.search(r'version\s*=\s*(["\'][^"\']+["\']|\w+)', block) if not name_match or not version_match: continue version = version_match.group(1) - if version.startswith('"'): - version = version.strip('"') + if version[0] in "\"'": + version = version.strip("\"'") else: version = variables.get(version, "") versions[name_match.group(1)] = version.split(".bcr.")[0] @@ -111,7 +113,7 @@ def versions_cmake(versions): def main(): manifest_path, out_root = sys.argv[1], sys.argv[2] - with open(manifest_path) as f: + with open(manifest_path, encoding="utf-8") as f: manifest = json.load(f) assembler = Assembler(out_root) From 6a2566ed0326051adb3b427d1166ca8a14c11cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 10 Jul 2026 15:26:31 +0200 Subject: [PATCH 10/14] cmake-deps: keep _FORTIFY_SOURCE defined in every build type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit boost 1.89 asio keys the ip::detail::endpoint layout on defined(_FORTIFY_SOURCE): 128 bytes when defined, 28 when not. The prebuilt archives are compiled with bazel's -D_FORTIFY_SOURCE=1, so a CMake TU compiled without the define allocates the small endpoint and the archive's out-of-line ctor (BOOST_ASIO_SEPARATE_COMPILATION) zero-fills 128 bytes, overflowing the caller's frame. In PrometheusMetricsServer::RunServer the clobbered neighbor was the io_context — service_registry_ became null and the server thread died on a call through it (TestCFileUtils Utl.metrics_server_responds_with_basic_metric). The wrappers now inject _FORTIFY_SOURCE themselves instead of leaving it to the (stripped) toolchain flag set: value 1 under optimization, value 0 otherwise, so defined() — the ABI key — holds in every CMake build type while glibc's fortify-requires-optimization warning stays off at -O0. Verified: a standalone io_context+acceptor repro linked against the pool segfaults with the old wrapper and passes at -O0 and -O2 with this one. Signed-off-by: Øyvind Harboe --- cmake-deps/defs.bzl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cmake-deps/defs.bzl b/cmake-deps/defs.bzl index c5bad7bd3a3..b6c4c51fbc2 100644 --- a/cmake-deps/defs.bzl +++ b/cmake-deps/defs.bzl @@ -31,6 +31,14 @@ load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") # Flags owned by the CMake build type, not by the toolchain. Everything else # extracted from the bazel toolchain is kept verbatim. +# +# -D_FORTIFY_SOURCE is stripped here but re-injected by the wrapper with a +# value keyed to the optimization level: in boost 1.89 defined(_FORTIFY_SOURCE) +# selects a 128-byte asio ip::detail::endpoint layout the prebuilt archives +# are compiled with, so the define must be present in every build type or the +# archive's endpoint ctor overflows the caller's smaller stack object (ABI +# break). Value 0 under -O0 keeps defined() true while satisfying glibc's +# "fortify requires optimization" check. _FLAG_DENYLIST_PREFIXES = [ "-O", "-g", @@ -226,11 +234,15 @@ def _wrapper_script(compiler, flags, link_args, defines): "set -u", 'R="$(cd "$(dirname "$0")/.." && pwd)"', "link=1", + 'fortify="-D_FORTIFY_SOURCE=0"', 'for arg in "$@"; do', ' case "$arg" in', " -c|-S|-E|-M|-MM|--version|-v|-dumpversion|-dumpmachine|--help|-print-*)", " link=0", " ;;", + " -O|-O1|-O2|-O3|-Os|-Oz|-Ofast|-Og)", + ' fortify="-D_FORTIFY_SOURCE=1"', + " ;;", " esac", "done", "post=()", @@ -248,7 +260,7 @@ def _wrapper_script(compiler, flags, link_args, defines): lines.append( 'exec "${R}/llvm/' + _map_exec_path(compiler) + '" \\\n ' + " \\\n ".join(args) + - ' \\\n "$@" ${post[@]+"${post[@]}"}', + ' \\\n "${fortify}" "$@" ${post[@]+"${post[@]}"}', ) return "\n".join(lines) + "\n" From cb9173468857bcd39691041271b178b14e955705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 10 Jul 2026 15:36:08 +0200 Subject: [PATCH 11/14] cmake-deps: generate the uniform package config shims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eight of the ten shims were the same shape — interface target over the merged include tree and the archive pool, a version from deps-versions.cmake, aliases, a few variables. They are now emitted from a declarative _SHIM_SPECS table in defs.bzl; every package with a version key also gets an AnyNewerVersion ConfigVersion.cmake (Boost's hand-written one generalized). Adding a dependency is one spec entry instead of a new file. BoostConfig.cmake (component loop) and GTestConfig.cmake (static imports of the non-pooled archives) keep their files. Signed-off-by: Øyvind Harboe --- cmake-deps/defs.bzl | 154 +++++++++++++++++++++- cmake-deps/shims/BoostConfigVersion.cmake | 16 --- cmake-deps/shims/Eigen3Config.cmake | 18 --- cmake-deps/shims/LEMONConfig.cmake | 13 -- cmake-deps/shims/abslConfig.cmake | 19 --- cmake-deps/shims/fmtConfig.cmake | 22 ---- cmake-deps/shims/ortoolsConfig.cmake | 18 --- cmake-deps/shims/spdlogConfig.cmake | 20 --- cmake-deps/shims/yaml-cppConfig.cmake | 20 --- 9 files changed, 153 insertions(+), 147 deletions(-) delete mode 100644 cmake-deps/shims/BoostConfigVersion.cmake delete mode 100644 cmake-deps/shims/Eigen3Config.cmake delete mode 100644 cmake-deps/shims/LEMONConfig.cmake delete mode 100644 cmake-deps/shims/abslConfig.cmake delete mode 100644 cmake-deps/shims/fmtConfig.cmake delete mode 100644 cmake-deps/shims/ortoolsConfig.cmake delete mode 100644 cmake-deps/shims/spdlogConfig.cmake delete mode 100644 cmake-deps/shims/yaml-cppConfig.cmake diff --git a/cmake-deps/defs.bzl b/cmake-deps/defs.bzl index b6c4c51fbc2..aa802816568 100644 --- a/cmake-deps/defs.bzl +++ b/cmake-deps/defs.bzl @@ -315,6 +315,158 @@ def _shim_package(basename): return basename[:-len(suffix)] fail("shim file name must end in Config.cmake or ConfigVersion.cmake: " + basename) +def _shim_target(name, pool = True, defines = []): + return struct(name = name, pool = pool, defines = defines) + +# Generated package config shims: interface targets over the merged +# include tree and (usually) the archive pool, a _VERSION from +# deps-versions.cmake, aliases and extra variables. Packages with bespoke +# shapes (Boost's component loop, GTest's static imports) keep files in +# shims/; every package with a version key also gets a generated +# AnyNewerVersion ConfigVersion.cmake. +_SHIM_SPECS = { + "Eigen3": struct( + version = "eigen", + targets = [_shim_target("Eigen3::Eigen", pool = False)], + aliases = {}, + variables = { + "EIGEN3_INCLUDE_DIR": "${_OR_DEPS}/include", + "EIGEN3_INCLUDE_DIRS": "${_OR_DEPS}/include", + }, + ), + "LEMON": struct( + version = "coin_or_lemon", + targets = [], + aliases = {}, + variables = { + "LEMON_INCLUDE_DIR": "${_OR_DEPS}/include", + "LEMON_INCLUDE_DIRS": "${_OR_DEPS}/include", + "LEMON_LIBRARIES": "openroad_deps::pool", + }, + ), + "absl": struct( + version = None, + targets = [ + _shim_target("absl::" + name) + for name in ["city", "hash", "strings", "synchronization", "base", "time", "span"] + ], + aliases = {}, + variables = {}, + ), + "fmt": struct( + version = "fmt", + targets = [ + _shim_target("fmt::fmt"), + _shim_target( + "fmt::fmt-header-only", + pool = False, + defines = ["FMT_HEADER_ONLY=1"], + ), + ], + aliases = {}, + variables = {}, + ), + "ortools": struct( + version = "or_tools", + targets = [_shim_target("ortools::ortools")], + aliases = {}, + variables = {}, + ), + "spdlog": struct( + version = "spdlog", + targets = [_shim_target("spdlog::spdlog")], + aliases = {"spdlog::spdlog_header_only": "spdlog::spdlog"}, + variables = {}, + ), + "yaml-cpp": struct( + version = "yaml_cpp", + targets = [_shim_target("yaml-cpp")], + aliases = {"yaml-cpp::yaml-cpp": "yaml-cpp"}, + variables = {}, + ), +} + +# File-based shims that still want a generated ConfigVersion.cmake. +_SHIM_EXTRA_VERSIONS = { + "Boost": "boost", + "GTest": "googletest", +} + +def _shim_config_cmake(pkg, spec): + lines = [ + "# Generated by //cmake-deps (_SHIM_SPECS in defs.bzl).", + "", + 'include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake")', + ] + if spec.version: + lines.append('include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake")') + lines.append("") + lines.append('set({}_VERSION "${{OPENROAD_DEPS_VERSION_{}}}")'.format(pkg, spec.version)) + for var, value in spec.variables.items(): + lines.append('set({} "{}")'.format(var, value)) + for target in spec.targets: + lines += [ + "", + "if(NOT TARGET {})".format(target.name), + " add_library({} INTERFACE IMPORTED GLOBAL)".format(target.name), + " set_target_properties({} PROPERTIES".format(target.name), + ' INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include"', + ] + if target.pool: + lines.append(" INTERFACE_LINK_LIBRARIES openroad_deps::pool") + if target.defines: + lines.append(" INTERFACE_COMPILE_DEFINITIONS \"{}\"".format( + ";".join(target.defines), + )) + lines += [" )", "endif()"] + for alias, target in spec.aliases.items(): + lines += [ + "", + "if(NOT TARGET {})".format(alias), + " add_library({} ALIAS {})".format(alias, target), + "endif()", + ] + return "\n".join(lines) + "\n" + +def _shim_config_version_cmake(version): + return "\n".join([ + "# Generated by //cmake-deps. AnyNewerVersion semantics.", + "", + 'include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake")', + "", + 'set(PACKAGE_VERSION "${OPENROAD_DEPS_VERSION_%s}")' % version, + "if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)", + " set(PACKAGE_VERSION_COMPATIBLE FALSE)", + "else()", + " set(PACKAGE_VERSION_COMPATIBLE TRUE)", + " if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)", + " set(PACKAGE_VERSION_EXACT TRUE)", + " endif()", + "endif()", + ]) + "\n" + +def _shim_writes(): + writes = [] + for pkg, spec in _SHIM_SPECS.items(): + writes.append(struct( + dest = "lib/cmake/{}/{}Config.cmake".format(pkg, pkg), + content = _shim_config_cmake(pkg, spec), + executable = False, + )) + versions = dict(_SHIM_EXTRA_VERSIONS) + versions.update({ + pkg: spec.version + for pkg, spec in _SHIM_SPECS.items() + if spec.version + }) + for pkg, version in versions.items(): + writes.append(struct( + dest = "lib/cmake/{}/{}ConfigVersion.cmake".format(pkg, pkg), + content = _shim_config_version_cmake(version), + executable = False, + )) + return writes + def _cmake_deps_bundle_impl(ctx): cc_toolchain = find_cc_toolchain(ctx) toolchain = _toolchain_command_lines(ctx, cc_toolchain) @@ -552,7 +704,7 @@ def _cmake_deps_bundle_impl(ctx): content = tool_wrappers["flex"], executable = True, ), - ] + ] + _shim_writes() manifest = struct( copies = [struct(dest = dest, src = src) for dest, src in copies.items()], diff --git a/cmake-deps/shims/BoostConfigVersion.cmake b/cmake-deps/shims/BoostConfigVersion.cmake deleted file mode 100644 index cb4e28608dd..00000000000 --- a/cmake-deps/shims/BoostConfigVersion.cmake +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026, The OpenROAD Authors -# -# AnyNewerVersion semantics for the Boost config shim. - -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") - -set(PACKAGE_VERSION "${OPENROAD_DEPS_VERSION_boost}") -if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) - set(PACKAGE_VERSION_COMPATIBLE FALSE) -else() - set(PACKAGE_VERSION_COMPATIBLE TRUE) - if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) - set(PACKAGE_VERSION_EXACT TRUE) - endif() -endif() diff --git a/cmake-deps/shims/Eigen3Config.cmake b/cmake-deps/shims/Eigen3Config.cmake deleted file mode 100644 index bae7ae9ac3a..00000000000 --- a/cmake-deps/shims/Eigen3Config.cmake +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026, The OpenROAD Authors -# -# Config shim for bazel-materialized Eigen (header-only). - -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") - -set(Eigen3_VERSION "${OPENROAD_DEPS_VERSION_eigen}") -set(EIGEN3_INCLUDE_DIR "${_OR_DEPS}/include") -set(EIGEN3_INCLUDE_DIRS "${_OR_DEPS}/include") - -if(NOT TARGET Eigen3::Eigen) - add_library(Eigen3::Eigen INTERFACE IMPORTED GLOBAL) - set_target_properties(Eigen3::Eigen PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" - ) -endif() diff --git a/cmake-deps/shims/LEMONConfig.cmake b/cmake-deps/shims/LEMONConfig.cmake deleted file mode 100644 index 8259da12c27..00000000000 --- a/cmake-deps/shims/LEMONConfig.cmake +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026, The OpenROAD Authors -# -# Config shim for bazel-materialized COIN-OR LEMON. OpenROAD only consumes -# ${LEMON_INCLUDE_DIRS}; the compiled objects are in the archive pool. - -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") - -set(LEMON_VERSION "${OPENROAD_DEPS_VERSION_coin_or_lemon}") -set(LEMON_INCLUDE_DIR "${_OR_DEPS}/include") -set(LEMON_INCLUDE_DIRS "${_OR_DEPS}/include") -set(LEMON_LIBRARIES openroad_deps::pool) diff --git a/cmake-deps/shims/abslConfig.cmake b/cmake-deps/shims/abslConfig.cmake deleted file mode 100644 index 46e7f1b929a..00000000000 --- a/cmake-deps/shims/abslConfig.cmake +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026, The OpenROAD Authors -# -# Config shim for bazel-materialized Abseil. Only the targets OpenROAD's -# CMake files reference are defined; all are interfaces over the merged -# include tree + archive pool. - -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") - -foreach(_or_absl_target city hash strings synchronization base time span) - if(NOT TARGET absl::${_or_absl_target}) - add_library(absl::${_or_absl_target} INTERFACE IMPORTED GLOBAL) - set_target_properties(absl::${_or_absl_target} PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" - INTERFACE_LINK_LIBRARIES openroad_deps::pool - ) - endif() -endforeach() -unset(_or_absl_target) diff --git a/cmake-deps/shims/fmtConfig.cmake b/cmake-deps/shims/fmtConfig.cmake deleted file mode 100644 index d0e840a7904..00000000000 --- a/cmake-deps/shims/fmtConfig.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026, The OpenROAD Authors -# -# Config shim for bazel-materialized fmt. - -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") - -set(fmt_VERSION "${OPENROAD_DEPS_VERSION_fmt}") - -if(NOT TARGET fmt::fmt) - add_library(fmt::fmt INTERFACE IMPORTED GLOBAL) - set_target_properties(fmt::fmt PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" - INTERFACE_LINK_LIBRARIES openroad_deps::pool - ) - add_library(fmt::fmt-header-only INTERFACE IMPORTED GLOBAL) - set_target_properties(fmt::fmt-header-only PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" - INTERFACE_COMPILE_DEFINITIONS FMT_HEADER_ONLY=1 - ) -endif() diff --git a/cmake-deps/shims/ortoolsConfig.cmake b/cmake-deps/shims/ortoolsConfig.cmake deleted file mode 100644 index b9ec1ab7284..00000000000 --- a/cmake-deps/shims/ortoolsConfig.cmake +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026, The OpenROAD Authors -# -# Config shim for bazel-materialized OR-Tools (with scip, highs, glpk, -# soplex, bliss, protobuf and abseil in the archive pool). - -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") - -set(ortools_VERSION "${OPENROAD_DEPS_VERSION_or_tools}") - -if(NOT TARGET ortools::ortools) - add_library(ortools::ortools INTERFACE IMPORTED GLOBAL) - set_target_properties(ortools::ortools PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" - INTERFACE_LINK_LIBRARIES openroad_deps::pool - ) -endif() diff --git a/cmake-deps/shims/spdlogConfig.cmake b/cmake-deps/shims/spdlogConfig.cmake deleted file mode 100644 index 1945a04d34d..00000000000 --- a/cmake-deps/shims/spdlogConfig.cmake +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026, The OpenROAD Authors -# -# Config shim for bazel-materialized spdlog (header-only in the bazel -# graph, with external fmt; SPDLOG_FMT_EXTERNAL is injected globally by -# the compiler wrappers). - -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") - -set(spdlog_VERSION "${OPENROAD_DEPS_VERSION_spdlog}") - -if(NOT TARGET spdlog::spdlog) - add_library(spdlog::spdlog INTERFACE IMPORTED GLOBAL) - set_target_properties(spdlog::spdlog PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" - INTERFACE_LINK_LIBRARIES openroad_deps::pool - ) - add_library(spdlog::spdlog_header_only ALIAS spdlog::spdlog) -endif() diff --git a/cmake-deps/shims/yaml-cppConfig.cmake b/cmake-deps/shims/yaml-cppConfig.cmake deleted file mode 100644 index fdf74b53672..00000000000 --- a/cmake-deps/shims/yaml-cppConfig.cmake +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2026, The OpenROAD Authors -# -# Config shim for bazel-materialized yaml-cpp. Upstream's config defines -# the plain `yaml-cpp` target (odb/3dblox links it) plus the namespaced -# alias. - -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-pool.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/../openroad_deps/deps-versions.cmake") - -set(yaml-cpp_VERSION "${OPENROAD_DEPS_VERSION_yaml_cpp}") - -if(NOT TARGET yaml-cpp) - add_library(yaml-cpp INTERFACE IMPORTED GLOBAL) - set_target_properties(yaml-cpp PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_OR_DEPS}/include" - INTERFACE_LINK_LIBRARIES openroad_deps::pool - ) - add_library(yaml-cpp::yaml-cpp ALIAS yaml-cpp) -endif() From b62cbab24d2293af16bbca9c47d856f3a27bbd8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 10 Jul 2026 15:36:58 +0200 Subject: [PATCH 12/14] cmake-deps: bundle pinned cmake and ninja release binaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Host cmake is traditionally too old for OpenROAD, and the bundle already carries every other build tool. A module extension pins the official cmake 3.31.9 linux-x86_64 release layout (staged under deps/cmake/ so it finds its module tree; bin/cmake and bin/ctest are wrappers) and the ninja 1.12.1 binary at deps/bin/ninja. Host requirements shrink to bazelisk, git and bash. Signed-off-by: Øyvind Harboe --- MODULE.bazel | 4 ++++ README.md | 15 ++++++------ cmake-deps/BUILD.bazel | 2 ++ cmake-deps/defs.bzl | 30 ++++++++++++++++++++++++ cmake-deps/materialize.sh | 12 +++++----- cmake-deps/tools.bzl | 49 +++++++++++++++++++++++++++++++++++++++ docs/user/Build.md | 13 ++++++----- 7 files changed, 106 insertions(+), 19 deletions(-) create mode 100644 cmake-deps/tools.bzl diff --git a/MODULE.bazel b/MODULE.bazel index 3526b0ba0d9..4fca190e465 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -358,6 +358,10 @@ npm.npm_translate_lock( ) use_repo(npm, "npm") +# Pinned cmake/ninja release binaries for the //:cmake dependency bundle. +cmake_tools = use_extension("//cmake-deps:tools.bzl", "cmake_tools") +use_repo(cmake_tools, "cmake-linux-x86_64", "ninja-linux-x86_64") + orfs = use_extension("@bazel-orfs//:extension.bzl", "orfs_repositories", dev_dependency = True) orfs.default( # Use OpenROAD of this repo instead of the one bundled with @orfs diff --git a/README.md b/README.md index c950f7c9c28..f685cc5eb24 100644 --- a/README.md +++ b/README.md @@ -282,16 +282,17 @@ from [here](docs/user/Build.md). ### CMake build with Bazel-provided dependencies -On Linux x86_64, all CMake dependencies — compiler included — can be -materialized into a local `deps/` folder from the pinned Bazel module -graph, instead of running `etc/DependencyInstaller.sh`. The only host -requirements are `bazelisk`, `cmake`, `ninja` (or `make`), `git` and -`bash`; no `sudo`, and nothing is installed outside the workspace: +On Linux x86_64, all CMake dependencies — compiler, cmake and ninja +included — can be materialized into a local `deps/` folder from the +pinned Bazel module graph, instead of running +`etc/DependencyInstaller.sh`. The only host requirements are +`bazelisk`, `git` and `bash`; no `sudo`, and nothing is installed +outside the workspace: ``` shell bazelisk run //:cmake -cmake -DCMAKE_TOOLCHAIN_FILE=deps/toolchain.cmake -B build . -cmake --build build -j$(nproc) +deps/bin/cmake -DCMAKE_TOOLCHAIN_FILE=deps/toolchain.cmake -G Ninja -B build . +deps/bin/cmake --build build -j$(nproc) ``` The developer workflow after that is plain CMake. See diff --git a/cmake-deps/BUILD.bazel b/cmake-deps/BUILD.bazel index 3e174cd4ca9..3729ecaa07e 100644 --- a/cmake-deps/BUILD.bazel +++ b/cmake-deps/BUILD.bazel @@ -18,6 +18,7 @@ py_binary( # through their transitive compilation contexts. cmake_deps_bundle( name = "bundle", + cmake_files = "@cmake-linux-x86_64//:all_files", exclude_from_pool = ["googletest"], include_overrides = { # cudd's util.h etc. would collide in a flat include/; FindCUDD @@ -31,6 +32,7 @@ cmake_deps_bundle( "openmp": "omp", "zlib": "z", }, + ninja_binary = "@ninja-linux-x86_64//:ninja_bin", python_headers = "@rules_python//python/cc:current_py_cc_headers", python_libs = "@rules_python//python/cc:current_py_cc_libs", shims = glob(["shims/*.cmake"]), diff --git a/cmake-deps/defs.bzl b/cmake-deps/defs.bzl index aa802816568..e1771a75ab5 100644 --- a/cmake-deps/defs.bzl +++ b/cmake-deps/defs.bzl @@ -651,6 +651,16 @@ def _cmake_deps_bundle_impl(ctx): version_info = py_runtime.interpreter_version_info python_version = "{}.{}".format(version_info.major, version_info.minor) + # --- cmake and ninja: pinned official release binaries. --- + # cmake resolves its module tree relative to the real binary, so the + # whole release layout is staged and bin/ carries wrappers. + for f in ctx.files.cmake_files: + copies["cmake/" + _repo_relative_path(f)] = f.path + inputs.append(depset(ctx.files.cmake_files)) + ninja = ctx.file.ninja_binary + copies["bin/ninja"] = ninja.path + inputs.append(depset([ninja])) + # --- CMake package config shims. --- for f in ctx.files.shims: copies["lib/cmake/{}/{}".format(_shim_package(f.basename), f.basename)] = f.path @@ -704,6 +714,16 @@ def _cmake_deps_bundle_impl(ctx): content = tool_wrappers["flex"], executable = True, ), + struct( + dest = "bin/cmake", + content = _tool_wrapper_script("$R/cmake/bin/cmake", {}), + executable = True, + ), + struct( + dest = "bin/ctest", + content = _tool_wrapper_script("$R/cmake/bin/ctest", {}), + executable = True, + ), ] + _shim_writes() manifest = struct( @@ -741,6 +761,11 @@ cmake_deps_bundle = rule( implementation = _cmake_deps_bundle_impl, doc = "Assemble a CMake dependency prefix from bazel-built dependencies.", attrs = { + "cmake_files": attr.label( + allow_files = True, + mandatory = True, + doc = "The pinned cmake release layout (bin/ and share/).", + ), "deps": attr.label_list( providers = [CcInfo], doc = "Dependencies whose headers and archives go into the prefix.", @@ -759,6 +784,11 @@ cmake_deps_bundle = rule( "lib/lib.a copies for CMake Find modules that " + "search by name (FindZLIB: z, FindCUDD: cudd).", ), + "ninja_binary": attr.label( + allow_single_file = True, + mandatory = True, + doc = "The pinned ninja binary.", + ), "python_headers": attr.label( providers = [CcInfo], mandatory = True, diff --git a/cmake-deps/materialize.sh b/cmake-deps/materialize.sh index 0e81559cba9..63ecc567972 100755 --- a/cmake-deps/materialize.sh +++ b/cmake-deps/materialize.sh @@ -58,16 +58,16 @@ ABS_DEST="$(realpath "$DEST")" echo "" echo "CMake dependency prefix ready: $ABS_DEST" echo "" -echo "Build OpenROAD with plain CMake:" +echo "Build OpenROAD with plain CMake (cmake and ninja are bundled):" echo "" -echo " cmake -DCMAKE_TOOLCHAIN_FILE=$ABS_DEST/toolchain.cmake -B build ." -echo " cmake --build build -j\$(nproc)" +echo " $ABS_DEST/bin/cmake -DCMAKE_TOOLCHAIN_FILE=$ABS_DEST/toolchain.cmake -G Ninja -B build ." +echo " $ABS_DEST/bin/cmake --build build -j\$(nproc)" echo "" echo "Run the result with the bundled Tcl and Python runtimes:" echo "" echo " export TCL_LIBRARY=$ABS_DEST/lib/tcl9.0" echo " export PYTHONHOME=$ABS_DEST/python" -echo " ./build/src/openroad" +echo " ./build/bin/openroad" echo "" -echo "Host requirements: cmake >= 3.16, ninja or make, git, bash." -echo "Linux x86_64 only; the GUI is not part of this prefix." +echo "Host requirements: git, bash. Linux x86_64 only; the GUI is not" +echo "part of this prefix." diff --git a/cmake-deps/tools.bzl b/cmake-deps/tools.bzl new file mode 100644 index 00000000000..f1ebd77e23b --- /dev/null +++ b/cmake-deps/tools.bzl @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors + +"""Pinned official cmake and ninja release binaries for the deps bundle. + +Host cmake is traditionally too old for OpenROAD; the bundle ships its +own, so a plain-CMake build needs no host toolchain packages at all. +Linux x86_64 only, like the rest of the bundle. +""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +CMAKE_VERSION = "3.31.9" +NINJA_VERSION = "1.12.1" + +def _cmake_tools_impl(_mctx): + http_archive( + name = "cmake-linux-x86_64", + urls = [ + "https://github.com/Kitware/CMake/releases/download/v{v}/cmake-{v}-linux-x86_64.tar.gz".format(v = CMAKE_VERSION), + ], + sha256 = "312d78e0b7c5c9b4a97a87a46c3e525aa84895fe8135c238c4616bba73dc9518", + strip_prefix = "cmake-{v}-linux-x86_64".format(v = CMAKE_VERSION), + build_file_content = """ +filegroup( + name = "all_files", + srcs = glob(["bin/*", "share/cmake-*/**"]), + visibility = ["//visibility:public"], +) +""", + ) + http_archive( + name = "ninja-linux-x86_64", + urls = [ + "https://github.com/ninja-build/ninja/releases/download/v{v}/ninja-linux.zip".format(v = NINJA_VERSION), + ], + sha256 = "6f98805688d19672bd699fbbfa2c2cf0fc054ac3df1f0e6a47664d963d530255", + build_file_content = """ +filegroup( + name = "ninja_bin", + srcs = ["ninja"], + visibility = ["//visibility:public"], +) +""", + ) + +cmake_tools = module_extension( + implementation = _cmake_tools_impl, +) diff --git a/docs/user/Build.md b/docs/user/Build.md index 6c8a34c7d13..115fafe4f08 100644 --- a/docs/user/Build.md +++ b/docs/user/Build.md @@ -99,14 +99,15 @@ it can be uploaded in the "Relevant log output" section of OpenROAD On Linux x86_64, the pinned dependency set of the Bazel build can be materialized into a local `deps/` folder and used for a plain CMake -build — no `sudo`, no distro packages beyond `cmake`, `ninja` (or -`make`), `git` and `bash`, and no compiler: `deps/` includes the same -hermetic clang/libc++ toolchain the Bazel build uses. +build — no `sudo` and no distro packages beyond `git` and `bash`: +`deps/` includes the same hermetic clang/libc++ toolchain the Bazel +build uses, plus pinned `cmake` and `ninja` release binaries (host +cmake is traditionally too old for OpenROAD). ``` shell bazelisk run //:cmake -cmake -DCMAKE_TOOLCHAIN_FILE=deps/toolchain.cmake -B build . -cmake --build build -j$(nproc) +deps/bin/cmake -DCMAKE_TOOLCHAIN_FILE=deps/toolchain.cmake -G Ninja -B build . +deps/bin/cmake --build build -j$(nproc) ``` The resulting binary uses the bundled Tcl and Python runtimes: @@ -114,7 +115,7 @@ The resulting binary uses the bundled Tcl and Python runtimes: ``` shell export TCL_LIBRARY=$PWD/deps/lib/tcl9.0 export PYTHONHOME=$PWD/deps/python -./build/src/openroad +./build/bin/openroad ``` Notes: From f8c9a67de1b94cee2f6c79058c7c089719a075a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 10 Jul 2026 16:10:51 +0200 Subject: [PATCH 13/14] bazel: update MODULE.bazel.lock for the cmake_tools extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Øyvind Harboe --- MODULE.bazel.lock | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 558140e95af..c02ab85e1dc 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -820,6 +820,38 @@ }, "selectedYankedVersions": {}, "moduleExtensions": { + "//cmake-deps:tools.bzl%cmake_tools": { + "general": { + "bzlTransitiveDigest": "p8SLUUJx+/BjjdZsiYBzIb7ycWHlbTnCqaSltKafoDY=", + "usagesDigest": "gyDMccYrlKAbitA4qkBkvg9+Lzh7samlIYMXM0G+t8w=", + "recordedInputs": [ + "REPO_MAPPING:,bazel_tools bazel_tools" + ], + "generatedRepoSpecs": { + "cmake-linux-x86_64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/Kitware/CMake/releases/download/v3.31.9/cmake-3.31.9-linux-x86_64.tar.gz" + ], + "sha256": "312d78e0b7c5c9b4a97a87a46c3e525aa84895fe8135c238c4616bba73dc9518", + "strip_prefix": "cmake-3.31.9-linux-x86_64", + "build_file_content": "\nfilegroup(\n name = \"all_files\",\n srcs = glob([\"bin/*\", \"share/cmake-*/**\"]),\n visibility = [\"//visibility:public\"],\n)\n" + } + }, + "ninja-linux-x86_64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip" + ], + "sha256": "6f98805688d19672bd699fbbfa2c2cf0fc054ac3df1f0e6a47664d963d530255", + "build_file_content": "\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n visibility = [\"//visibility:public\"],\n)\n" + } + } + } + } + }, "@@apple_rules_lint+//lint:extensions.bzl%linter": { "general": { "bzlTransitiveDigest": "g7izj5kLCmsajh8IospHh4ZQ35dyM0FIrA8D4HapAsM=", From da88b8befd851fb3e6ffcd399c1042bdf9f83b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 10 Jul 2026 16:49:44 +0200 Subject: [PATCH 14/14] cmake-deps: parser and path-rewrite hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Strip comments before parsing MODULE.bazel: a ')' inside a comment would truncate a bazel_dep block and silently drop its version. - Error out on a bazel_dep version referencing an undefined variable instead of emitting an empty version. - Rewrite paths after a comma separator (-Wl,-rpath,bazel-out/...), not only after '=' and short options. - _archive_dest drops the bazel-out configuration segment for main-repository archives; correct the stale duplicate-header comment (first source wins at analysis time). deps-versions.cmake, deps-pool.cmake and the compiler wrappers are byte-identical before and after. Review feedback from gemini-code-assist on #10822 and #10872. Signed-off-by: Øyvind Harboe --- cmake-deps/assemble_bundle.py | 12 +++++++++--- cmake-deps/defs.bzl | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/cmake-deps/assemble_bundle.py b/cmake-deps/assemble_bundle.py index cb32d26f678..f035453f969 100644 --- a/cmake-deps/assemble_bundle.py +++ b/cmake-deps/assemble_bundle.py @@ -78,10 +78,11 @@ def parse_versions(module_bazel_path): """Dependency versions from MODULE.bazel, .bcr suffixes stripped. Matches each bazel_dep(...) block as a whole, so argument order and - buildifier's line wrapping do not matter. + buildifier's line wrapping do not matter. Comments are stripped + first: a ')' inside a comment would otherwise truncate a block. """ with open(module_bazel_path, encoding="utf-8") as f: - text = f.read() + text = re.sub(r"#.*", "", f.read()) versions = {} variables = dict( re.findall(r'^\s*(\w+)\s*=\s*["\']([^"\']+)["\']', text, re.MULTILINE) @@ -94,8 +95,13 @@ def parse_versions(module_bazel_path): version = version_match.group(1) if version[0] in "\"'": version = version.strip("\"'") + elif version in variables: + version = variables[version] else: - version = variables.get(version, "") + raise SystemExit( + f"error: variable '{version}' referenced in bazel_dep " + f"but not defined in {module_bazel_path}" + ) versions[name_match.group(1)] = version.split(".bcr.")[0] for name, version in list(versions.items()): if name.startswith("boost."): diff --git a/cmake-deps/defs.bzl b/cmake-deps/defs.bzl index e1771a75ab5..0def7c9fa16 100644 --- a/cmake-deps/defs.bzl +++ b/cmake-deps/defs.bzl @@ -99,11 +99,13 @@ def _rewrite_flag(flag, subtree): prefix, found, rest = flag.partition(marker) # Only rewrite when the marker starts a path: at the start of the - # flag, after '=' (--sysroot=external/...), or after a short - # option (-Lbazel-out/..., -Bbazel-out/...). + # flag, after '=' (--sysroot=external/...), after a comma + # (-Wl,-rpath,bazel-out/...), or after a short option + # (-Lbazel-out/..., -Bbazel-out/...). if found and ( prefix == "" or prefix.endswith("=") or + prefix.endswith(",") or (prefix.startswith("-") and "/" not in prefix) ): return prefix + "$R/" + subtree + "/" + _map_exec_path(marker + rest) @@ -216,9 +218,14 @@ def _header_dest(header, sorted_roots, include_overrides): return None def _archive_dest(library_file): - """Destination of a static archive inside lib/pool/.""" + """Destination of a static archive inside lib/pool/. + + Main-repository archives have no external/ segment; their bazel-out + configuration prefix is dropped so the layout stays + configuration-independent. + """ _, _, rest = library_file.path.partition("external/") - return "lib/pool/" + (rest or library_file.path) + return "lib/pool/" + _map_exec_path(rest or library_file.path) def _wrapper_script(compiler, flags, link_args, defines): """A relocatable compiler driver wrapper. @@ -522,8 +529,8 @@ def _cmake_deps_bundle_impl(ctx): continue if copies.get(dest, header.path) != header.path: # Same header staged via several include roots (e.g. - # protobuf _virtual_includes); the assembler verifies the - # contents are identical. + # protobuf _virtual_includes); first one wins at analysis + # time, so only one source reaches the assembler. continue copies[dest] = header.path